data/ContactStore.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "ContactStore.h" | ||
| 2 | |||
| 3 | #include "ContactIndex.h" | ||
| 4 | #include "ContactPeopleStore.h" | ||
| 5 | #include "ContactSqlValue.h" | ||
| 6 | #include "DatabaseSecurity.h" | ||
| 7 | #include "VCardCodec.h" | ||
| 8 | |||
| 9 | #include <QFile> | ||
| 10 | #include <QLoggingCategory> | ||
| 11 | #include <QSet> | ||
| 12 | #include <QSqlError> | ||
| 13 | #include <QSqlQuery> | ||
| 14 | #include <QUuid> | ||
| 15 | |||
| 16 |
4/7✓ Branch 2 → 3 taken 9 times.
✓ Branch 2 → 8 taken 47 times.
✓ Branch 4 → 5 taken 9 times.
✗ Branch 4 → 8 not taken.
✓ Branch 5 → 6 taken 9 times.
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 12 not taken.
|
56 | Q_LOGGING_CATEGORY(lcContactStore, "mailjd.contactstore") |
| 17 | |||
| 18 | namespace { | ||
| 19 | |||
| 20 | const char *kCardColumns = "id, book_id, uid, href, etag, raw, base_raw, sync_state, writable"; | ||
| 21 | const char *kBookColumns = | ||
| 22 | "id, account_id, path, display_name, read_only, ctag, sync_token, supports_sync_collection, " | ||
| 23 | "supported_versions, max_resource_size, fetched, last_sync, last_error, show_in_all, use_for_completion, " | ||
| 24 | "skipped_cards"; | ||
| 25 | const char *kOutboxColumns = | ||
| 26 | "id, book_id, uid, operation, href, base_etag, revision, attempts, next_attempt, last_error, state, " | ||
| 27 | "server_raw, server_etag, conflict_fields, depends_on, base_raw"; | ||
| 28 | |||
| 29 | 2791 | StoredCard cardFrom(const QSqlQuery &q) { | |
| 30 | 2791 | StoredCard card; | |
| 31 |
2/2✓ Branch 2 → 3 taken 2791 times.
✓ Branch 3 → 4 taken 2791 times.
|
2791 | card.id = q.value(0).toLongLong(); |
| 32 |
2/2✓ Branch 5 → 6 taken 2791 times.
✓ Branch 6 → 7 taken 2791 times.
|
2791 | card.bookId = q.value(1).toLongLong(); |
| 33 |
2/2✓ Branch 8 → 9 taken 2791 times.
✓ Branch 9 → 10 taken 2791 times.
|
2791 | card.uid = q.value(2).toString(); |
| 34 |
2/2✓ Branch 13 → 14 taken 2791 times.
✓ Branch 14 → 15 taken 2791 times.
|
2791 | card.href = q.value(3).toString(); |
| 35 |
2/2✓ Branch 18 → 19 taken 2791 times.
✓ Branch 19 → 20 taken 2791 times.
|
2791 | card.etag = q.value(4).toString(); |
| 36 |
2/2✓ Branch 23 → 24 taken 2791 times.
✓ Branch 24 → 25 taken 2791 times.
|
2791 | card.raw = q.value(5).toString(); |
| 37 |
2/2✓ Branch 28 → 29 taken 2791 times.
✓ Branch 29 → 30 taken 2791 times.
|
2791 | card.baseRaw = q.value(6).toString(); |
| 38 |
2/2✓ Branch 33 → 34 taken 2791 times.
✓ Branch 34 → 35 taken 2791 times.
|
2791 | card.syncState = q.value(7).toString(); |
| 39 |
2/2✓ Branch 38 → 39 taken 2791 times.
✓ Branch 39 → 40 taken 2791 times.
|
2791 | card.writable = q.value(8).toBool(); |
| 40 | 2791 | return card; | |
| 41 | ✗ | } | |
| 42 | |||
| 43 | 25433 | AddressBookInfo bookFrom(const QSqlQuery &q) { | |
| 44 | 25433 | AddressBookInfo book; | |
| 45 |
2/2✓ Branch 3 → 4 taken 25433 times.
✓ Branch 4 → 5 taken 25433 times.
|
25433 | book.id = q.value(0).toLongLong(); |
| 46 |
2/2✓ Branch 6 → 7 taken 25433 times.
✓ Branch 7 → 8 taken 25433 times.
|
25433 | book.accountId = q.value(1).toString(); |
| 47 |
2/2✓ Branch 11 → 12 taken 25433 times.
✓ Branch 12 → 13 taken 25433 times.
|
25433 | book.path = q.value(2).toString(); |
| 48 |
2/2✓ Branch 16 → 17 taken 25433 times.
✓ Branch 17 → 18 taken 25433 times.
|
25433 | book.displayName = q.value(3).toString(); |
| 49 |
2/2✓ Branch 21 → 22 taken 25433 times.
✓ Branch 22 → 23 taken 25433 times.
|
25433 | book.readOnly = q.value(4).toBool(); |
| 50 |
2/2✓ Branch 24 → 25 taken 25433 times.
✓ Branch 25 → 26 taken 25433 times.
|
25433 | book.ctag = q.value(5).toString(); |
| 51 |
2/2✓ Branch 29 → 30 taken 25433 times.
✓ Branch 30 → 31 taken 25433 times.
|
25433 | book.syncToken = q.value(6).toString(); |
| 52 |
2/2✓ Branch 34 → 35 taken 25433 times.
✓ Branch 35 → 36 taken 25433 times.
|
25433 | book.supportsSyncCollection = q.value(7).toBool(); |
| 53 |
3/3✓ Branch 37 → 38 taken 25433 times.
✓ Branch 38 → 39 taken 25433 times.
✓ Branch 42 → 43 taken 25433 times.
|
25433 | book.supportedVersions = q.value(8).toString().split(QLatin1Char(','), Qt::SkipEmptyParts); |
| 54 |
2/2✓ Branch 47 → 48 taken 25433 times.
✓ Branch 48 → 49 taken 25433 times.
|
25433 | book.maxResourceSize = q.value(9).toLongLong(); |
| 55 |
2/2✓ Branch 50 → 51 taken 25433 times.
✓ Branch 51 → 52 taken 25433 times.
|
25433 | book.fetched = q.value(10).toBool(); |
| 56 |
3/3✓ Branch 53 → 54 taken 25433 times.
✓ Branch 54 → 55 taken 25433 times.
✓ Branch 55 → 56 taken 25433 times.
|
25433 | book.lastSync = QDateTime::fromString(q.value(11).toString(), Qt::ISODate); |
| 57 |
2/2✓ Branch 60 → 61 taken 25433 times.
✓ Branch 61 → 62 taken 25433 times.
|
25433 | book.lastError = q.value(12).toString(); |
| 58 |
2/2✓ Branch 65 → 66 taken 25433 times.
✓ Branch 66 → 67 taken 25433 times.
|
25433 | book.showInAll = q.value(13).toBool(); |
| 59 |
2/2✓ Branch 68 → 69 taken 25433 times.
✓ Branch 69 → 70 taken 25433 times.
|
25433 | book.useForCompletion = q.value(14).toBool(); |
| 60 |
2/2✓ Branch 71 → 72 taken 25433 times.
✓ Branch 72 → 73 taken 25433 times.
|
25433 | book.skippedCards = q.value(15).toInt(); |
| 61 | 25433 | return book; | |
| 62 | ✗ | } | |
| 63 | |||
| 64 | 1940 | ContactOutboxEntry outboxFrom(const QSqlQuery &q) { | |
| 65 | 1940 | ContactOutboxEntry entry; | |
| 66 |
2/2✓ Branch 3 → 4 taken 1940 times.
✓ Branch 4 → 5 taken 1940 times.
|
1940 | entry.id = q.value(0).toLongLong(); |
| 67 |
2/2✓ Branch 6 → 7 taken 1940 times.
✓ Branch 7 → 8 taken 1940 times.
|
1940 | entry.bookId = q.value(1).toLongLong(); |
| 68 |
2/2✓ Branch 9 → 10 taken 1940 times.
✓ Branch 10 → 11 taken 1940 times.
|
1940 | entry.uid = q.value(2).toString(); |
| 69 |
2/2✓ Branch 14 → 15 taken 1940 times.
✓ Branch 15 → 16 taken 1940 times.
|
1940 | entry.operation = q.value(3).toString(); |
| 70 |
2/2✓ Branch 19 → 20 taken 1940 times.
✓ Branch 20 → 21 taken 1940 times.
|
1940 | entry.href = q.value(4).toString(); |
| 71 |
2/2✓ Branch 24 → 25 taken 1940 times.
✓ Branch 25 → 26 taken 1940 times.
|
1940 | entry.baseEtag = q.value(5).toString(); |
| 72 |
2/2✓ Branch 29 → 30 taken 1940 times.
✓ Branch 30 → 31 taken 1940 times.
|
1940 | entry.revision = q.value(6).toInt(); |
| 73 |
2/2✓ Branch 32 → 33 taken 1940 times.
✓ Branch 33 → 34 taken 1940 times.
|
1940 | entry.attempts = q.value(7).toInt(); |
| 74 |
2/2✓ Branch 35 → 36 taken 1940 times.
✓ Branch 36 → 37 taken 1940 times.
|
1940 | entry.nextAttempt = q.value(8).toLongLong(); |
| 75 |
2/2✓ Branch 38 → 39 taken 1940 times.
✓ Branch 39 → 40 taken 1940 times.
|
1940 | entry.lastError = q.value(9).toString(); |
| 76 |
2/2✓ Branch 43 → 44 taken 1940 times.
✓ Branch 44 → 45 taken 1940 times.
|
1940 | entry.state = q.value(10).toString(); |
| 77 |
2/2✓ Branch 48 → 49 taken 1940 times.
✓ Branch 49 → 50 taken 1940 times.
|
1940 | entry.serverRaw = q.value(11).toString(); |
| 78 |
2/2✓ Branch 53 → 54 taken 1940 times.
✓ Branch 54 → 55 taken 1940 times.
|
1940 | entry.serverEtag = q.value(12).toString(); |
| 79 |
3/3✓ Branch 58 → 59 taken 1940 times.
✓ Branch 59 → 60 taken 1940 times.
✓ Branch 63 → 64 taken 1940 times.
|
1940 | entry.conflictFields = q.value(13).toString().split(QLatin1Char(','), Qt::SkipEmptyParts); |
| 80 |
2/2✓ Branch 68 → 69 taken 1940 times.
✓ Branch 69 → 70 taken 1940 times.
|
1940 | entry.dependsOn = q.value(14).toLongLong(); |
| 81 |
2/2✓ Branch 71 → 72 taken 1940 times.
✓ Branch 72 → 73 taken 1940 times.
|
1940 | entry.baseRaw = q.value(15).toString(); |
| 82 | 1940 | return entry; | |
| 83 | ✗ | } | |
| 84 | |||
| 85 | 60856 | bool exec(QSqlDatabase &db, const QString &sql) { | |
| 86 |
1/1✓ Branch 2 → 3 taken 60856 times.
|
60856 | QSqlQuery q(db); |
| 87 |
3/3✓ Branch 3 → 4 taken 60856 times.
✓ Branch 4 → 5 taken 60837 times.
✓ Branch 4 → 6 taken 19 times.
|
60856 | if (q.exec(sql)) |
| 88 | 60837 | return true; | |
| 89 |
10/10✓ Branch 6 → 7 taken 19 times.
✓ Branch 10 → 11 taken 19 times.
✓ Branch 11 → 12 taken 19 times.
✓ Branch 12 → 13 taken 19 times.
✓ Branch 13 → 14 taken 19 times.
✓ Branch 14 → 15 taken 19 times.
✓ Branch 15 → 16 taken 19 times.
✓ Branch 16 → 17 taken 19 times.
✓ Branch 23 → 8 taken 19 times.
✓ Branch 23 → 24 taken 19 times.
|
38 | qCWarning(lcContactStore) << "Schema statement failed:" << q.lastError().text() << sql.left(60); |
| 90 | 19 | return false; | |
| 91 | 60856 | } | |
| 92 | |||
| 93 | const QStringList kSchema{ | ||
| 94 | QStringLiteral("CREATE TABLE IF NOT EXISTS address_books (" | ||
| 95 | " id INTEGER PRIMARY KEY AUTOINCREMENT, account_id TEXT NOT NULL DEFAULT ''," | ||
| 96 | " path TEXT NOT NULL, display_name TEXT NOT NULL DEFAULT '', read_only INTEGER NOT NULL DEFAULT 0," | ||
| 97 | " ctag TEXT NOT NULL DEFAULT '', sync_token TEXT NOT NULL DEFAULT ''," | ||
| 98 | " supports_sync_collection INTEGER NOT NULL DEFAULT 0, supported_versions TEXT NOT NULL DEFAULT ''," | ||
| 99 | " max_resource_size INTEGER NOT NULL DEFAULT 0, fetched INTEGER NOT NULL DEFAULT 0," | ||
| 100 | " last_sync TEXT NOT NULL DEFAULT '', last_error TEXT NOT NULL DEFAULT ''," | ||
| 101 | " show_in_all INTEGER NOT NULL DEFAULT 1, use_for_completion INTEGER NOT NULL DEFAULT 1," | ||
| 102 | " skipped_cards INTEGER NOT NULL DEFAULT 0, UNIQUE(account_id, path))"), | ||
| 103 | QStringLiteral("CREATE TABLE IF NOT EXISTS cards (" | ||
| 104 | " id INTEGER PRIMARY KEY AUTOINCREMENT," | ||
| 105 | " book_id INTEGER NOT NULL REFERENCES address_books(id) ON DELETE CASCADE," | ||
| 106 | " uid TEXT NOT NULL, href TEXT NOT NULL DEFAULT '', etag TEXT NOT NULL DEFAULT ''," | ||
| 107 | " raw TEXT NOT NULL, base_raw TEXT NOT NULL DEFAULT '', sync_state TEXT NOT NULL DEFAULT ''," | ||
| 108 | " writable INTEGER NOT NULL DEFAULT 1, version TEXT NOT NULL DEFAULT ''," | ||
| 109 | " kind TEXT NOT NULL DEFAULT 'individual', display_name TEXT NOT NULL DEFAULT ''," | ||
| 110 | " sort_display TEXT NOT NULL DEFAULT '', sort_family TEXT NOT NULL DEFAULT ''," | ||
| 111 | " section TEXT NOT NULL DEFAULT '#', organization TEXT NOT NULL DEFAULT ''," | ||
| 112 | " primary_address TEXT NOT NULL DEFAULT '', address_count INTEGER NOT NULL DEFAULT 0," | ||
| 113 | " has_photo INTEGER NOT NULL DEFAULT 0, photo_thumb BLOB," | ||
| 114 | " search_text TEXT NOT NULL DEFAULT '', updated_at TEXT NOT NULL DEFAULT (datetime('now'))," | ||
| 115 | " UNIQUE(book_id, uid))"), | ||
| 116 | QStringLiteral("CREATE INDEX IF NOT EXISTS idx_cards_sort ON cards(sort_display, id)"), | ||
| 117 | QStringLiteral("CREATE INDEX IF NOT EXISTS idx_cards_family ON cards(sort_family, id)"), | ||
| 118 | QStringLiteral("CREATE INDEX IF NOT EXISTS idx_cards_href ON cards(book_id, href)"), | ||
| 119 | // Group cards are few; groups() runs per completion keystroke and per | ||
| 120 | // sidebar refresh and must not scan every card (G10). | ||
| 121 | QStringLiteral("CREATE INDEX IF NOT EXISTS idx_cards_group_kind ON cards(book_id) WHERE kind = 'group'"), | ||
| 122 | QStringLiteral("CREATE TABLE IF NOT EXISTS card_addresses (" | ||
| 123 | " card_id INTEGER NOT NULL REFERENCES cards(id) ON DELETE CASCADE, position INTEGER NOT NULL," | ||
| 124 | " address TEXT NOT NULL, address_key TEXT NOT NULL, type TEXT NOT NULL DEFAULT ''," | ||
| 125 | " preferred INTEGER NOT NULL DEFAULT 0)"), | ||
| 126 | QStringLiteral("CREATE INDEX IF NOT EXISTS idx_card_addresses_key ON card_addresses(address_key)"), | ||
| 127 | QStringLiteral("CREATE INDEX IF NOT EXISTS idx_card_addresses_card ON card_addresses(card_id)"), | ||
| 128 | QStringLiteral("CREATE TABLE IF NOT EXISTS card_groups (" | ||
| 129 | " card_id INTEGER NOT NULL REFERENCES cards(id) ON DELETE CASCADE, name TEXT NOT NULL)"), | ||
| 130 | QStringLiteral("CREATE INDEX IF NOT EXISTS idx_card_groups_card ON card_groups(card_id)"), | ||
| 131 | QStringLiteral("CREATE INDEX IF NOT EXISTS idx_card_groups_name ON card_groups(name)"), | ||
| 132 | QStringLiteral("CREATE TABLE IF NOT EXISTS card_members (" | ||
| 133 | " card_id INTEGER NOT NULL REFERENCES cards(id) ON DELETE CASCADE, member TEXT NOT NULL)"), | ||
| 134 | QStringLiteral("CREATE INDEX IF NOT EXISTS idx_card_members_card ON card_members(card_id)"), | ||
| 135 | QStringLiteral("CREATE TABLE IF NOT EXISTS collected_addresses (" | ||
| 136 | " address_key TEXT PRIMARY KEY, address TEXT NOT NULL, display_name TEXT NOT NULL DEFAULT ''," | ||
| 137 | " first_used TEXT NOT NULL DEFAULT '', last_used TEXT NOT NULL DEFAULT '')"), | ||
| 138 | QStringLiteral("CREATE TABLE IF NOT EXISTS address_usage (" | ||
| 139 | " address_key TEXT PRIMARY KEY, use_count INTEGER NOT NULL DEFAULT 0," | ||
| 140 | " last_used TEXT NOT NULL DEFAULT '')"), | ||
| 141 | QStringLiteral("CREATE TABLE IF NOT EXISTS contact_outbox (" | ||
| 142 | " id INTEGER PRIMARY KEY AUTOINCREMENT, book_id INTEGER NOT NULL, uid TEXT NOT NULL," | ||
| 143 | " operation TEXT NOT NULL, href TEXT NOT NULL DEFAULT '', base_etag TEXT NOT NULL DEFAULT ''," | ||
| 144 | " revision INTEGER NOT NULL DEFAULT 1, attempts INTEGER NOT NULL DEFAULT 0," | ||
| 145 | " next_attempt INTEGER NOT NULL DEFAULT 0, last_error TEXT NOT NULL DEFAULT ''," | ||
| 146 | " state TEXT NOT NULL DEFAULT 'pending', server_raw TEXT NOT NULL DEFAULT ''," | ||
| 147 | " server_etag TEXT NOT NULL DEFAULT '', conflict_fields TEXT NOT NULL DEFAULT ''," | ||
| 148 | " depends_on INTEGER NOT NULL DEFAULT 0, base_raw TEXT NOT NULL DEFAULT ''," | ||
| 149 | " UNIQUE(book_id, uid))"), | ||
| 150 | // Sprint 107 D-21: the losing side of conflict resolutions. | ||
| 151 | ConflictHistory::createStatement(), | ||
| 152 | QStringLiteral("CREATE TABLE IF NOT EXISTS contact_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL DEFAULT '')"), | ||
| 153 | }; | ||
| 154 | |||
| 155 | } // namespace | ||
| 156 | |||
| 157 |
1/1✓ Branch 3 → 4 taken 4336 times.
|
4336 | ContactStore::ContactStore(QObject *parent) : QObject(parent) {} |
| 158 | |||
| 159 | 5323 | ContactStore::~ContactStore() { close(); } | |
| 160 | |||
| 161 | 3021 | bool ContactStore::open(const QString &dbPath) { | |
| 162 |
3/3✓ Branch 2 → 3 taken 3021 times.
✓ Branch 3 → 4 taken 3 times.
✓ Branch 3 → 16 taken 3018 times.
|
3021 | if (!DatabaseSecurity::preparePath(dbPath)) { |
| 163 |
6/6✓ Branch 4 → 5 taken 3 times.
✓ Branch 8 → 9 taken 3 times.
✓ Branch 9 → 10 taken 3 times.
✓ Branch 10 → 11 taken 3 times.
✓ Branch 14 → 6 taken 3 times.
✓ Branch 14 → 15 taken 3 times.
|
6 | qCWarning(lcContactStore) << "Failed to create private contacts DB:" << dbPath; |
| 164 | 3 | return false; | |
| 165 | } | ||
| 166 | 3018 | m_dbPath = dbPath; | |
| 167 |
3/3✓ Branch 17 → 18 taken 3018 times.
✓ Branch 18 → 19 taken 3018 times.
✓ Branch 22 → 23 taken 3018 times.
|
6036 | m_connectionName = QStringLiteral("contacts_") + QUuid::createUuid().toString(); |
| 168 |
2/2✓ Branch 31 → 32 taken 3018 times.
✓ Branch 32 → 33 taken 3018 times.
|
6036 | m_db = QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"), m_connectionName); |
| 169 |
1/1✓ Branch 36 → 37 taken 3018 times.
|
3018 | m_db.setDatabaseName(dbPath); |
| 170 |
2/3✓ Branch 37 → 38 taken 3018 times.
✗ Branch 38 → 39 not taken.
✓ Branch 38 → 55 taken 3018 times.
|
3018 | if (!m_db.open()) { |
| 171 | ✗ | qCWarning(lcContactStore) << "Failed to open contacts DB:" << m_db.lastError().text(); | |
| 172 | ✗ | return false; | |
| 173 | } | ||
| 174 |
2/3✓ Branch 55 → 56 taken 3018 times.
✗ Branch 56 → 57 not taken.
✓ Branch 56 → 70 taken 3018 times.
|
3018 | if (!DatabaseSecurity::restrictExistingFile(dbPath)) { |
| 175 | ✗ | qCWarning(lcContactStore) << "Failed to restrict contacts DB permissions:" << dbPath; | |
| 176 | ✗ | m_db.close(); | |
| 177 | ✗ | return false; | |
| 178 | } | ||
| 179 |
1/1✓ Branch 70 → 71 taken 3018 times.
|
3018 | QSqlQuery pragma(m_db); |
| 180 |
1/1✓ Branch 74 → 75 taken 3018 times.
|
3018 | pragma.exec(QStringLiteral("PRAGMA journal_mode=WAL")); |
| 181 |
1/1✓ Branch 80 → 81 taken 3018 times.
|
3018 | pragma.exec(QStringLiteral("PRAGMA synchronous=NORMAL")); |
| 182 | // T-619/SEC-19: Prevent SQLITE_BUSY errors during concurrent access | ||
| 183 |
1/1✓ Branch 86 → 87 taken 3018 times.
|
3018 | pragma.exec(QStringLiteral("PRAGMA busy_timeout=5000")); |
| 184 |
1/1✓ Branch 92 → 93 taken 3018 times.
|
3018 | pragma.exec(QStringLiteral("PRAGMA foreign_keys=ON")); |
| 185 |
1/1✓ Branch 95 → 96 taken 3018 times.
|
3018 | return createSchema(); |
| 186 | 3018 | } | |
| 187 | |||
| 188 | 4415 | void ContactStore::close() { | |
| 189 |
2/2✓ Branch 3 → 4 taken 3018 times.
✓ Branch 3 → 5 taken 1397 times.
|
4415 | if (m_db.isOpen()) |
| 190 | 3018 | m_db.close(); | |
| 191 | // T-402/Bug 20: Release reference before removeDatabase | ||
| 192 |
2/2✓ Branch 5 → 6 taken 4415 times.
✓ Branch 6 → 7 taken 4415 times.
|
4415 | m_db = QSqlDatabase(); |
| 193 |
2/2✓ Branch 9 → 10 taken 3018 times.
✓ Branch 9 → 12 taken 1397 times.
|
4415 | if (!m_connectionName.isEmpty()) { |
| 194 | 3018 | QSqlDatabase::removeDatabase(m_connectionName); | |
| 195 | 3018 | m_connectionName.clear(); | |
| 196 | } | ||
| 197 | 4415 | } | |
| 198 | |||
| 199 | 3793 | bool ContactStore::isOpen() const { return m_db.isOpen(); } | |
| 200 | |||
| 201 | 163 | bool ContactStore::beginTransaction() { return m_db.transaction(); } | |
| 202 | |||
| 203 | 160 | bool ContactStore::commitTransaction() { return m_db.commit(); } | |
| 204 | |||
| 205 | 3 | void ContactStore::rollbackTransaction() { m_db.rollback(); } | |
| 206 | |||
| 207 | 3018 | bool ContactStore::createSchema() { | |
| 208 |
1/1✓ Branch 2 → 3 taken 3018 times.
|
3018 | QSqlQuery version(m_db); |
| 209 | 3018 | int current = 0; | |
| 210 |
9/16✓ Branch 6 → 7 taken 3018 times.
✓ Branch 7 → 8 taken 3017 times.
✓ Branch 7 → 11 taken 1 time.
✓ Branch 8 → 9 taken 3017 times.
✓ Branch 9 → 10 taken 3017 times.
✗ Branch 9 → 11 not taken.
✓ Branch 12 → 13 taken 3018 times.
✗ Branch 12 → 14 not taken.
✓ Branch 14 → 15 taken 3018 times.
✗ Branch 14 → 16 not taken.
✓ Branch 16 → 17 taken 3017 times.
✓ Branch 16 → 21 taken 1 time.
✗ Branch 212 → 213 not taken.
✗ Branch 212 → 214 not taken.
✗ Branch 216 → 217 not taken.
✗ Branch 216 → 218 not taken.
|
6036 | if (version.exec(QStringLiteral("PRAGMA user_version")) && version.next()) |
| 211 |
2/2✓ Branch 17 → 18 taken 3017 times.
✓ Branch 18 → 19 taken 3017 times.
|
3017 | current = version.value(0).toInt(); |
| 212 |
1/1✓ Branch 21 → 22 taken 3018 times.
|
3018 | version.finish(); |
| 213 |
2/2✓ Branch 22 → 23 taken 2576 times.
✓ Branch 22 → 57 taken 442 times.
|
3018 | if (current >= SchemaVersion) { |
| 214 | // F-77 (SPRINT-104): the same-version path runs in a transaction too | ||
| 215 | // — its people-table rebuild (an interim v6 build without place_name) | ||
| 216 | // drops and backfills derived tables; interrupted outside a | ||
| 217 | // transaction, the shape check passed forever after while every | ||
| 218 | // people row of an untouched card stayed invisible. A failure now | ||
| 219 | // rolls the rebuild back as a whole. | ||
| 220 |
2/3✓ Branch 23 → 24 taken 2576 times.
✗ Branch 24 → 25 not taken.
✓ Branch 24 → 26 taken 2576 times.
|
2576 | if (!m_db.transaction()) |
| 221 | ✗ | return false; | |
| 222 | 2576 | bool ok = true; | |
| 223 |
2/2✓ Branch 36 → 28 taken 48944 times.
✓ Branch 36 → 37 taken 2576 times.
|
51520 | for (const QString &statement : kSchema) |
| 224 |
3/5✓ Branch 29 → 30 taken 48944 times.
✓ Branch 30 → 31 taken 48944 times.
✗ Branch 30 → 33 not taken.
✓ Branch 31 → 32 taken 48944 times.
✗ Branch 31 → 33 not taken.
|
48944 | ok = exec(m_db, statement) && ok; // additive, idempotent; a failure is reported (S106-166) |
| 225 |
4/5✓ Branch 37 → 38 taken 2576 times.
✗ Branch 37 → 41 not taken.
✓ Branch 38 → 39 taken 2576 times.
✓ Branch 39 → 40 taken 2575 times.
✓ Branch 39 → 41 taken 1 time.
|
2576 | ok = ok && ContactPeopleStore::createSchema(m_db); |
| 226 |
2/2✓ Branch 42 → 43 taken 1 time.
✓ Branch 42 → 55 taken 2575 times.
|
2576 | if (!ok) { |
| 227 |
5/5✓ Branch 43 → 44 taken 1 time.
✓ Branch 47 → 48 taken 1 time.
✓ Branch 48 → 49 taken 1 time.
✓ Branch 52 → 45 taken 1 time.
✓ Branch 52 → 53 taken 1 time.
|
2 | qCWarning(lcContactStore) << "Contact schema check failed; rolled back"; |
| 228 |
1/1✓ Branch 53 → 54 taken 1 time.
|
1 | m_db.rollback(); |
| 229 | 1 | return false; | |
| 230 | } | ||
| 231 |
1/1✓ Branch 55 → 56 taken 2575 times.
|
2575 | return m_db.commit(); |
| 232 | } | ||
| 233 | 442 | bool hasLegacy = false; | |
| 234 | { | ||
| 235 |
1/1✓ Branch 57 → 58 taken 442 times.
|
442 | QSqlQuery tables(m_db); |
| 236 |
1/1✓ Branch 61 → 62 taken 442 times.
|
442 | tables.exec(QStringLiteral("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'contacts'")); |
| 237 |
1/1✓ Branch 64 → 65 taken 442 times.
|
442 | hasLegacy = tables.next(); |
| 238 | 442 | } | |
| 239 | // Only the first schema (0 → v1) converts the pre-Sprint-97 table; a | ||
| 240 | // later step must not convert it again while it still waits to be dropped. | ||
| 241 |
4/4✓ Branch 66 → 67 taken 8 times.
✓ Branch 66 → 69 taken 434 times.
✓ Branch 67 → 68 taken 7 times.
✓ Branch 67 → 69 taken 1 time.
|
442 | const bool convertLegacy = hasLegacy && current == 0; |
| 242 |
4/4✓ Branch 70 → 71 taken 4 times.
✓ Branch 70 → 73 taken 438 times.
✓ Branch 71 → 72 taken 2 times.
✓ Branch 71 → 73 taken 2 times.
|
442 | const bool backfillPeople = current > 0 && current < 6; // S103.1.4: derive the v6 people rows once |
| 243 |
7/8✓ Branch 74 → 75 taken 435 times.
✓ Branch 74 → 76 taken 7 times.
✓ Branch 75 → 76 taken 2 times.
✓ Branch 75 → 79 taken 433 times.
✓ Branch 77 → 78 taken 9 times.
✗ Branch 77 → 79 not taken.
✓ Branch 80 → 81 taken 9 times.
✓ Branch 80 → 150 taken 433 times.
|
442 | if ((convertLegacy || backfillPeople) && !m_dbPath.isEmpty()) { |
| 244 | // Pre-update backup; .pre-v1 is removed after the old table is dropped. | ||
| 245 |
7/11✓ Branch 81 → 82 taken 7 times.
✓ Branch 81 → 85 taken 2 times.
✓ Branch 88 → 89 taken 9 times.
✓ Branch 90 → 91 taken 2 times.
✓ Branch 90 → 92 taken 7 times.
✓ Branch 92 → 93 taken 7 times.
✓ Branch 92 → 94 taken 2 times.
✗ Branch 240 → 241 not taken.
✗ Branch 240 → 242 not taken.
✗ Branch 244 → 245 not taken.
✗ Branch 244 → 246 not taken.
|
18 | const QString backup = m_dbPath + (convertLegacy ? QStringLiteral(".pre-v1") : QStringLiteral(".pre-v6")); |
| 246 |
1/1✓ Branch 94 → 95 taken 9 times.
|
9 | QSqlQuery checkpoint(m_db); |
| 247 |
1/1✓ Branch 98 → 99 taken 9 times.
|
9 | checkpoint.exec(QStringLiteral("PRAGMA wal_checkpoint(TRUNCATE)")); |
| 248 | // S106-166: the new copy is complete before an older backup goes. | ||
| 249 |
1/1✓ Branch 104 → 105 taken 9 times.
|
9 | const QString fresh = backup + QStringLiteral(".new"); |
| 250 |
1/1✓ Branch 107 → 108 taken 9 times.
|
9 | QFile::remove(fresh); |
| 251 |
3/4✓ Branch 108 → 109 taken 9 times.
✓ Branch 110 → 111 taken 9 times.
✓ Branch 111 → 112 taken 9 times.
✗ Branch 111 → 119 not taken.
|
18 | if (QFile::copy(m_dbPath, fresh) && DatabaseSecurity::restrictExistingFile(fresh) && |
| 252 |
6/13✓ Branch 109 → 110 taken 9 times.
✗ Branch 109 → 119 not taken.
✓ Branch 112 → 113 taken 9 times.
✗ Branch 113 → 114 not taken.
✓ Branch 113 → 116 taken 9 times.
✗ Branch 114 → 115 not taken.
✗ Branch 115 → 116 not taken.
✗ Branch 115 → 119 not taken.
✓ Branch 116 → 117 taken 9 times.
✓ Branch 117 → 118 taken 9 times.
✗ Branch 117 → 119 not taken.
✓ Branch 120 → 121 taken 9 times.
✗ Branch 120 → 133 not taken.
|
18 | (!QFile::exists(backup) || QFile::remove(backup)) && QFile::rename(fresh, backup)) { |
| 253 |
6/6✓ Branch 121 → 122 taken 9 times.
✓ Branch 125 → 126 taken 9 times.
✓ Branch 126 → 127 taken 9 times.
✓ Branch 127 → 128 taken 9 times.
✓ Branch 131 → 123 taken 9 times.
✓ Branch 131 → 132 taken 9 times.
|
18 | qCInfo(lcContactStore) << "Pre-migration backup written:" << backup; |
| 254 | } else { | ||
| 255 | ✗ | QFile::remove(fresh); | |
| 256 | ✗ | qCWarning(lcContactStore) << "Pre-migration backup could not be written:" << backup; | |
| 257 | } | ||
| 258 | 9 | } | |
| 259 |
2/3✓ Branch 150 → 151 taken 442 times.
✗ Branch 151 → 152 not taken.
✓ Branch 151 → 153 taken 442 times.
|
442 | if (!m_db.transaction()) |
| 260 | ✗ | return false; | |
| 261 | 442 | bool ok = true; | |
| 262 |
2/2✓ Branch 163 → 155 taken 8398 times.
✓ Branch 163 → 164 taken 442 times.
|
8840 | for (const QString &statement : kSchema) |
| 263 |
4/5✓ Branch 156 → 157 taken 8398 times.
✓ Branch 157 → 158 taken 8379 times.
✓ Branch 157 → 160 taken 19 times.
✓ Branch 158 → 159 taken 8379 times.
✗ Branch 158 → 160 not taken.
|
8398 | ok = exec(m_db, statement) && ok; |
| 264 |
6/8✓ Branch 164 → 165 taken 441 times.
✓ Branch 164 → 170 taken 1 time.
✓ Branch 165 → 166 taken 441 times.
✓ Branch 166 → 167 taken 441 times.
✗ Branch 166 → 170 not taken.
✓ Branch 167 → 168 taken 441 times.
✓ Branch 168 → 169 taken 441 times.
✗ Branch 168 → 170 not taken.
|
442 | ok = ok && addMissingColumns() && ContactPeopleStore::createSchema(m_db); |
| 265 |
6/7✓ Branch 171 → 172 taken 441 times.
✓ Branch 171 → 176 taken 1 time.
✓ Branch 172 → 173 taken 2 times.
✓ Branch 172 → 175 taken 439 times.
✓ Branch 173 → 174 taken 2 times.
✓ Branch 174 → 175 taken 2 times.
✗ Branch 174 → 176 not taken.
|
442 | ok = ok && (!backfillPeople || ContactPeopleStore::backfill(m_db)); |
| 266 |
4/4✓ Branch 177 → 178 taken 441 times.
✓ Branch 177 → 181 taken 1 time.
✓ Branch 178 → 179 taken 7 times.
✓ Branch 178 → 181 taken 434 times.
|
442 | if (ok && convertLegacy) |
| 267 |
1/1✓ Branch 179 → 180 taken 7 times.
|
7 | ok = convertLegacyContacts(); |
| 268 |
2/2✓ Branch 181 → 182 taken 441 times.
✓ Branch 181 → 192 taken 1 time.
|
442 | if (ok) |
| 269 |
2/2✓ Branch 186 → 187 taken 441 times.
✓ Branch 187 → 188 taken 441 times.
|
882 | ok = exec(m_db, QStringLiteral("PRAGMA user_version = %1").arg(SchemaVersion)); |
| 270 |
2/2✓ Branch 192 → 193 taken 1 time.
✓ Branch 192 → 207 taken 441 times.
|
442 | if (!ok) { |
| 271 |
1/1✓ Branch 193 → 194 taken 1 time.
|
1 | m_db.rollback(); |
| 272 |
7/7✓ Branch 194 → 195 taken 1 time.
✓ Branch 198 → 199 taken 1 time.
✓ Branch 199 → 200 taken 1 time.
✓ Branch 200 → 201 taken 1 time.
✓ Branch 201 → 202 taken 1 time.
✓ Branch 205 → 196 taken 1 time.
✓ Branch 205 → 206 taken 1 time.
|
2 | qCWarning(lcContactStore) << "Contact schema version" << SchemaVersion << "could not be created"; |
| 273 | 1 | return false; | |
| 274 | } | ||
| 275 |
1/1✓ Branch 207 → 208 taken 441 times.
|
441 | return m_db.commit(); |
| 276 | 3018 | } | |
| 277 | |||
| 278 | // Columns added after v1 (CREATE TABLE IF NOT EXISTS keeps an existing | ||
| 279 | // table): v2 (S97.3.1) `skipped_cards` separates unreadable cards from a | ||
| 280 | // failed sync (`last_error`); v3 (S97.5.10) `cards.birthday`; v4 (S97.5.7) | ||
| 281 | // `cards.photo_uri` and `photo_fetched_uri`; v5 (F100-10) `phone`, | ||
| 282 | // `job_title`, `locality`; v7 `anniversary`. | ||
| 283 | 441 | bool ContactStore::addMissingColumns() { | |
| 284 | 1323 | const auto columns = [this](const QString &table) { | |
| 285 |
1/1✓ Branch 2 → 3 taken 1323 times.
|
1323 | QSqlQuery info(m_db); |
| 286 | 1323 | QStringList names; | |
| 287 |
3/4✓ Branch 7 → 8 taken 1323 times.
✓ Branch 8 → 9 taken 1323 times.
✓ Branch 12 → 13 taken 1323 times.
✗ Branch 12 → 22 not taken.
|
2646 | if (info.exec(QStringLiteral("PRAGMA table_info(%1)").arg(table))) |
| 288 |
3/3✓ Branch 20 → 21 taken 25162 times.
✓ Branch 21 → 14 taken 23839 times.
✓ Branch 21 → 22 taken 1323 times.
|
25162 | while (info.next()) |
| 289 |
3/3✓ Branch 14 → 15 taken 23839 times.
✓ Branch 15 → 16 taken 23839 times.
✓ Branch 16 → 17 taken 23839 times.
|
23839 | names.append(info.value(1).toString()); |
| 290 | 1323 | return names; | |
| 291 | 1323 | }; | |
| 292 | 441 | bool ok = true; | |
| 293 |
3/3✓ Branch 5 → 6 taken 441 times.
✓ Branch 11 → 12 taken 1 time.
✓ Branch 11 → 19 taken 440 times.
|
441 | if (!columns(QStringLiteral("address_books")).contains(QLatin1String("skipped_cards"))) |
| 294 |
1/1✓ Branch 15 → 16 taken 1 time.
|
1 | ok = exec(m_db, QStringLiteral("ALTER TABLE address_books ADD COLUMN skipped_cards INTEGER NOT NULL DEFAULT 0")); |
| 295 |
1/1✓ Branch 22 → 23 taken 441 times.
|
441 | const QStringList cardColumns = columns(QStringLiteral("cards")); |
| 296 | // v3 (S97.5.10): `birthday` ("YYYY-MM-DD" / "--MM-DD") for the calendar's | ||
| 297 | // birthday layer. | ||
| 298 |
5/6✓ Branch 25 → 26 taken 441 times.
✗ Branch 25 → 30 not taken.
✓ Branch 28 → 29 taken 437 times.
✓ Branch 28 → 30 taken 4 times.
✓ Branch 31 → 32 taken 437 times.
✓ Branch 31 → 41 taken 4 times.
|
441 | if (ok && !cardColumns.contains(QLatin1String("birthday"))) |
| 299 |
1/1✓ Branch 36 → 37 taken 437 times.
|
874 | ok = addDerivedColumn(QStringLiteral("birthday"), [](const ContactCard &card) { |
| 300 | ✗ | return card.birthday.isValid() ? card.birthday.toIso() : QString(); | |
| 301 | }); | ||
| 302 | // v4 (S97.5.7): `photo_uri` of a URI PHOTO; `photo_fetched_uri` names the | ||
| 303 | // URI `photo_thumb` was fetched (or tried) for. | ||
| 304 |
5/6✓ Branch 41 → 42 taken 441 times.
✗ Branch 41 → 46 not taken.
✓ Branch 44 → 45 taken 437 times.
✓ Branch 44 → 46 taken 4 times.
✓ Branch 47 → 48 taken 437 times.
✓ Branch 47 → 57 taken 4 times.
|
441 | if (ok && !cardColumns.contains(QLatin1String("photo_uri"))) |
| 305 |
1/1✓ Branch 52 → 53 taken 437 times.
|
874 | ok = addDerivedColumn(QStringLiteral("photo_uri"), [](const ContactCard &card) { |
| 306 | ✗ | return card.photo.kind == ContactPhoto::Kind::Uri ? card.photo.uri : QString(); | |
| 307 | }); | ||
| 308 |
5/6✓ Branch 57 → 58 taken 441 times.
✗ Branch 57 → 62 not taken.
✓ Branch 60 → 61 taken 437 times.
✓ Branch 60 → 62 taken 4 times.
✓ Branch 63 → 64 taken 437 times.
✓ Branch 63 → 71 taken 4 times.
|
441 | if (ok && !cardColumns.contains(QLatin1String("photo_fetched_uri"))) |
| 309 |
1/1✓ Branch 67 → 68 taken 437 times.
|
437 | ok = exec(m_db, QStringLiteral("ALTER TABLE cards ADD COLUMN photo_fetched_uri TEXT NOT NULL DEFAULT ''")); |
| 310 | // v5 (F100-10): the list's optional columns phone, job title and city. | ||
| 311 |
5/6✓ Branch 71 → 72 taken 441 times.
✗ Branch 71 → 76 not taken.
✓ Branch 74 → 75 taken 437 times.
✓ Branch 74 → 76 taken 4 times.
✓ Branch 77 → 78 taken 437 times.
✓ Branch 77 → 87 taken 4 times.
|
441 | if (ok && !cardColumns.contains(QLatin1String("phone"))) |
| 312 |
1/1✓ Branch 82 → 83 taken 437 times.
|
874 | ok = addDerivedColumn(QStringLiteral("phone"), [](const ContactCard &card) { return ContactIndex::primaryPhone(card); }); |
| 313 |
5/6✓ Branch 87 → 88 taken 441 times.
✗ Branch 87 → 92 not taken.
✓ Branch 90 → 91 taken 437 times.
✓ Branch 90 → 92 taken 4 times.
✓ Branch 93 → 94 taken 437 times.
✓ Branch 93 → 103 taken 4 times.
|
441 | if (ok && !cardColumns.contains(QLatin1String("job_title"))) |
| 314 |
1/1✓ Branch 98 → 99 taken 437 times.
|
874 | ok = addDerivedColumn(QStringLiteral("job_title"), [](const ContactCard &card) { return card.title.trimmed(); }); |
| 315 |
5/6✓ Branch 103 → 104 taken 441 times.
✗ Branch 103 → 108 not taken.
✓ Branch 106 → 107 taken 437 times.
✓ Branch 106 → 108 taken 4 times.
✓ Branch 109 → 110 taken 437 times.
✓ Branch 109 → 119 taken 4 times.
|
441 | if (ok && !cardColumns.contains(QLatin1String("locality"))) |
| 316 |
1/1✓ Branch 114 → 115 taken 437 times.
|
874 | ok = addDerivedColumn(QStringLiteral("locality"), [](const ContactCard &card) { return ContactIndex::primaryLocality(card); }); |
| 317 | // v8 (Sprint 107 D-22): `contact_outbox.base_raw` keeps the last server | ||
| 318 | // version of a moved card's source; the merge of a source changed on the | ||
| 319 | // server needs it after the source row is gone. | ||
| 320 |
9/19✓ Branch 119 → 120 taken 441 times.
✗ Branch 119 → 128 not taken.
✓ Branch 123 → 124 taken 441 times.
✓ Branch 126 → 127 taken 1 time.
✓ Branch 126 → 128 taken 440 times.
✓ Branch 129 → 130 taken 441 times.
✗ Branch 129 → 131 not taken.
✓ Branch 131 → 132 taken 441 times.
✗ Branch 131 → 133 not taken.
✓ Branch 133 → 134 taken 441 times.
✗ Branch 133 → 135 not taken.
✓ Branch 135 → 136 taken 1 time.
✓ Branch 135 → 143 taken 440 times.
✗ Branch 238 → 239 not taken.
✗ Branch 238 → 240 not taken.
✗ Branch 242 → 243 not taken.
✗ Branch 242 → 244 not taken.
✗ Branch 246 → 247 not taken.
✗ Branch 246 → 248 not taken.
|
882 | if (ok && !columns(QStringLiteral("contact_outbox")).contains(QLatin1String("base_raw"))) |
| 321 |
1/1✓ Branch 139 → 140 taken 1 time.
|
1 | ok = exec(m_db, QStringLiteral("ALTER TABLE contact_outbox ADD COLUMN base_raw TEXT NOT NULL DEFAULT ''")); |
| 322 | // v7: `anniversary` (as `birthday`) for the wedding year on the relationship graph's partner lines. | ||
| 323 |
5/6✓ Branch 143 → 144 taken 441 times.
✗ Branch 143 → 148 not taken.
✓ Branch 146 → 147 taken 438 times.
✓ Branch 146 → 148 taken 3 times.
✓ Branch 149 → 150 taken 438 times.
✓ Branch 149 → 159 taken 3 times.
|
441 | if (ok && !cardColumns.contains(QLatin1String("anniversary"))) |
| 324 |
1/1✓ Branch 154 → 155 taken 438 times.
|
876 | ok = addDerivedColumn(QStringLiteral("anniversary"), [](const ContactCard &card) { |
| 325 |
2/2✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 5 taken 1 time.
|
2 | return card.anniversary.isValid() ? card.anniversary.toIso() : QString(); |
| 326 | }); | ||
| 327 | 441 | return ok; | |
| 328 | 441 | } | |
| 329 | |||
| 330 | 2623 | bool ContactStore::addDerivedColumn(const QString &column, const std::function<QString(const ContactCard &)> &value) { | |
| 331 |
3/4✓ Branch 6 → 7 taken 2623 times.
✓ Branch 7 → 8 taken 2623 times.
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 13 taken 2623 times.
|
5246 | if (!exec(m_db, QStringLiteral("ALTER TABLE cards ADD COLUMN %1 TEXT NOT NULL DEFAULT ''").arg(column))) |
| 332 | ✗ | return false; | |
| 333 |
1/1✓ Branch 13 → 14 taken 2623 times.
|
2623 | QSqlQuery all(m_db); |
| 334 | 2623 | QList<QPair<qint64, QString>> rows; | |
| 335 |
2/3✓ Branch 17 → 18 taken 2623 times.
✓ Branch 20 → 21 taken 2623 times.
✗ Branch 20 → 35 not taken.
|
2623 | if (all.exec(QStringLiteral("SELECT id, raw FROM cards"))) |
| 336 |
3/3✓ Branch 33 → 34 taken 2625 times.
✓ Branch 34 → 22 taken 2 times.
✓ Branch 34 → 35 taken 2623 times.
|
2625 | while (all.next()) |
| 337 |
5/5✓ Branch 22 → 23 taken 2 times.
✓ Branch 23 → 24 taken 2 times.
✓ Branch 24 → 25 taken 2 times.
✓ Branch 25 → 26 taken 2 times.
✓ Branch 27 → 28 taken 2 times.
|
2 | rows.append({all.value(0).toLongLong(), all.value(1).toString()}); |
| 338 |
1/1✓ Branch 35 → 36 taken 2623 times.
|
2623 | all.finish(); |
| 339 | 2623 | bool ok = true; | |
| 340 |
4/4✓ Branch 36 → 37 taken 2623 times.
✓ Branch 37 → 38 taken 2623 times.
✓ Branch 84 → 39 taken 2 times.
✓ Branch 84 → 85 taken 2623 times.
|
2625 | for (const auto &row : rows) { |
| 341 |
1/1✓ Branch 40 → 41 taken 2 times.
|
2 | ContactCard card; |
| 342 |
3/4✓ Branch 41 → 42 taken 2 times.
✓ Branch 42 → 43 taken 2 times.
✗ Branch 42 → 44 not taken.
✓ Branch 43 → 45 taken 2 times.
|
2 | const QString derived = VCardCodec::parse(row.second, &card) ? value(card) : QString(); |
| 343 |
2/2✓ Branch 46 → 47 taken 1 time.
✓ Branch 46 → 48 taken 1 time.
|
2 | if (derived.isEmpty()) |
| 344 | 1 | continue; | |
| 345 |
1/1✓ Branch 48 → 49 taken 1 time.
|
1 | QSqlQuery update(m_db); |
| 346 |
2/2✓ Branch 53 → 54 taken 1 time.
✓ Branch 54 → 55 taken 1 time.
|
2 | update.prepare(QStringLiteral("UPDATE cards SET %1 = ? WHERE id = ?").arg(column)); |
| 347 |
1/1✓ Branch 60 → 61 taken 1 time.
|
1 | update.addBindValue(derived); |
| 348 |
1/1✓ Branch 64 → 65 taken 1 time.
|
1 | update.addBindValue(row.first); |
| 349 |
3/5✓ Branch 66 → 67 taken 1 time.
✓ Branch 67 → 68 taken 1 time.
✗ Branch 67 → 70 not taken.
✓ Branch 68 → 69 taken 1 time.
✗ Branch 68 → 70 not taken.
|
1 | ok = update.exec() && ok; |
| 350 | 3 | } | |
| 351 | 2623 | return ok; | |
| 352 | 2623 | } | |
| 353 | |||
| 354 | // D-5: see the class comment. Runs inside createSchema()'s transaction. | ||
| 355 | 7 | bool ContactStore::convertLegacyContacts() { | |
| 356 | 7 | QSet<QString> columns; | |
| 357 | { | ||
| 358 |
1/1✓ Branch 3 → 4 taken 7 times.
|
7 | QSqlQuery info(m_db); |
| 359 |
1/1✓ Branch 7 → 8 taken 7 times.
|
7 | info.exec(QStringLiteral("PRAGMA table_info(contacts)")); |
| 360 |
3/3✓ Branch 17 → 18 taken 101 times.
✓ Branch 18 → 11 taken 94 times.
✓ Branch 18 → 19 taken 7 times.
|
101 | while (info.next()) |
| 361 |
3/3✓ Branch 11 → 12 taken 94 times.
✓ Branch 12 → 13 taken 94 times.
✓ Branch 13 → 14 taken 94 times.
|
94 | columns.insert(info.value(1).toString()); |
| 362 | 7 | } | |
| 363 |
2/2✓ Branch 39 → 22 taken 28 times.
✓ Branch 39 → 40 taken 7 times.
|
35 | for (const char *column : {"carddav_href", "carddav_vuid", "carddav_account_id", "carddav_book_path"}) |
| 364 |
3/3✓ Branch 23 → 24 taken 28 times.
✓ Branch 26 → 27 taken 4 times.
✓ Branch 26 → 38 taken 24 times.
|
28 | if (!columns.contains(QLatin1String(column))) |
| 365 |
2/2✓ Branch 32 → 33 taken 4 times.
✓ Branch 33 → 34 taken 4 times.
|
8 | exec(m_db, QStringLiteral("ALTER TABLE contacts ADD COLUMN %1 TEXT DEFAULT ''").arg(QLatin1String(column))); |
| 366 |
1/1✓ Branch 43 → 44 taken 7 times.
|
7 | bool ok = exec(m_db, QStringLiteral( |
| 367 | "INSERT OR REPLACE INTO address_usage (address_key, use_count, last_used) " | ||
| 368 | "SELECT lower(trim(email)), SUM(use_count), COALESCE(MAX(last_used), '') FROM contacts " | ||
| 369 | "WHERE use_count > 0 AND trim(email) <> '' GROUP BY lower(trim(email))")); | ||
| 370 | // Server rows: one read-only legacy card per resource of each book. | ||
| 371 | struct LegacyCard { | ||
| 372 | qint64 bookId = 0; | ||
| 373 | QString uid; | ||
| 374 | QString href; | ||
| 375 | ContactCard card; | ||
| 376 | }; | ||
| 377 | 7 | QHash<QString, LegacyCard> legacy; | |
| 378 | 7 | QStringList order; | |
| 379 |
1/1✓ Branch 46 → 47 taken 7 times.
|
7 | QSqlQuery rows(m_db); |
| 380 |
1/1✓ Branch 50 → 51 taken 7 times.
|
7 | rows.exec(QStringLiteral( |
| 381 | "SELECT id, display_name, email, carddav_uid, carddav_href, carddav_vuid, carddav_account_id, " | ||
| 382 | "carddav_book_path FROM contacts WHERE source = 'carddav' ORDER BY id")); | ||
| 383 |
3/3✓ Branch 149 → 150 taken 17 times.
✓ Branch 150 → 54 taken 10 times.
✓ Branch 150 → 151 taken 7 times.
|
17 | while (rows.next()) { |
| 384 |
2/2✓ Branch 54 → 55 taken 10 times.
✓ Branch 55 → 56 taken 10 times.
|
10 | const QString account = rows.value(6).toString(); |
| 385 |
2/2✓ Branch 57 → 58 taken 10 times.
✓ Branch 58 → 59 taken 10 times.
|
10 | const QString path = rows.value(7).toString(); |
| 386 |
8/15✓ Branch 61 → 62 taken 8 times.
✓ Branch 61 → 64 taken 2 times.
✗ Branch 63 → 64 not taken.
✓ Branch 63 → 70 taken 8 times.
✓ Branch 68 → 69 taken 2 times.
✓ Branch 74 → 75 taken 2 times.
✓ Branch 74 → 76 taken 8 times.
✓ Branch 76 → 77 taken 2 times.
✓ Branch 76 → 78 taken 8 times.
✗ Branch 427 → 428 not taken.
✗ Branch 427 → 429 not taken.
✗ Branch 431 → 432 not taken.
✗ Branch 431 → 433 not taken.
✗ Branch 435 → 436 not taken.
✗ Branch 435 → 437 not taken.
|
22 | const qint64 bookId = account.isEmpty() || path.isEmpty() ? ensureBook(QString(), QStringLiteral("legacy")) |
| 387 |
3/3✓ Branch 70 → 71 taken 8 times.
✓ Branch 72 → 73 taken 2 times.
✓ Branch 72 → 74 taken 8 times.
|
10 | : ensureBook(account, path); |
| 388 |
2/2✓ Branch 78 → 79 taken 10 times.
✓ Branch 79 → 80 taken 10 times.
|
10 | QString uid = rows.value(5).toString(); |
| 389 |
2/2✓ Branch 82 → 83 taken 2 times.
✓ Branch 82 → 89 taken 8 times.
|
10 | if (uid.isEmpty()) |
| 390 |
2/2✓ Branch 83 → 84 taken 2 times.
✓ Branch 84 → 85 taken 2 times.
|
2 | uid = rows.value(3).toString(); |
| 391 |
1/2✗ Branch 90 → 91 not taken.
✓ Branch 90 → 104 taken 10 times.
|
10 | if (uid.isEmpty()) |
| 392 | ✗ | uid = QStringLiteral("legacy-%1").arg(rows.value(0).toLongLong()); | |
| 393 |
2/2✓ Branch 104 → 105 taken 10 times.
✓ Branch 105 → 106 taken 10 times.
|
10 | const QString href = rows.value(4).toString(); |
| 394 |
5/5✓ Branch 108 → 109 taken 2 times.
✓ Branch 108 → 110 taken 8 times.
✓ Branch 113 → 114 taken 10 times.
✓ Branch 114 → 115 taken 10 times.
✓ Branch 115 → 116 taken 10 times.
|
10 | const QString key = QString::number(bookId) + QLatin1Char('\n') + (href.isEmpty() ? uid : href); |
| 395 |
2/2✓ Branch 119 → 120 taken 8 times.
✓ Branch 119 → 133 taken 2 times.
|
10 | if (!legacy.contains(key)) { |
| 396 |
1/1✓ Branch 120 → 121 taken 8 times.
|
8 | LegacyCard entry; |
| 397 | 8 | entry.bookId = bookId; | |
| 398 | 8 | entry.uid = uid; | |
| 399 | 8 | entry.href = href; | |
| 400 | 8 | entry.card.uid = uid; | |
| 401 |
2/2✓ Branch 124 → 125 taken 8 times.
✓ Branch 125 → 126 taken 8 times.
|
8 | entry.card.formattedName = rows.value(1).toString(); |
| 402 |
1/1✓ Branch 129 → 130 taken 8 times.
|
8 | legacy.insert(key, entry); |
| 403 |
1/1✓ Branch 130 → 131 taken 8 times.
|
8 | order.append(key); |
| 404 | 8 | } | |
| 405 | 10 | ContactValue address; | |
| 406 |
2/2✓ Branch 133 → 134 taken 10 times.
✓ Branch 134 → 135 taken 10 times.
|
10 | address.value = rows.value(2).toString(); |
| 407 |
1/2✓ Branch 139 → 140 taken 10 times.
✗ Branch 139 → 142 not taken.
|
10 | if (!address.value.isEmpty()) |
| 408 |
2/2✓ Branch 140 → 141 taken 10 times.
✓ Branch 141 → 142 taken 10 times.
|
10 | legacy[key].card.emails.append(address); |
| 409 | 10 | } | |
| 410 |
4/4✓ Branch 151 → 152 taken 7 times.
✓ Branch 152 → 153 taken 7 times.
✓ Branch 183 → 154 taken 8 times.
✓ Branch 183 → 184 taken 7 times.
|
15 | for (const QString &key : order) { |
| 411 | 8 | const LegacyCard &entry = legacy.value(key); | |
| 412 | 8 | StoredCard card; | |
| 413 | 8 | card.bookId = entry.bookId; | |
| 414 | 8 | card.uid = entry.uid; | |
| 415 | 8 | card.href = entry.href; | |
| 416 |
1/1✓ Branch 162 → 163 taken 8 times.
|
16 | card.raw = VCardCodec::create(entry.card); |
| 417 | 8 | card.syncState = QStringLiteral("legacy"); | |
| 418 | 8 | card.writable = false; | |
| 419 |
3/5✓ Branch 174 → 175 taken 8 times.
✓ Branch 175 → 176 taken 8 times.
✗ Branch 175 → 178 not taken.
✓ Branch 176 → 177 taken 8 times.
✗ Branch 176 → 178 not taken.
|
8 | ok = putCard(card) > 0 && ok; |
| 420 | 8 | } | |
| 421 | // Local rows: collected (used when sending) or the user's own contacts. | ||
| 422 | 7 | QSet<QString> serverAddresses; | |
| 423 | { | ||
| 424 |
1/1✓ Branch 185 → 186 taken 7 times.
|
7 | QSqlQuery addresses(m_db); |
| 425 |
1/1✓ Branch 189 → 190 taken 7 times.
|
7 | addresses.exec(QStringLiteral("SELECT DISTINCT lower(trim(email)) FROM contacts WHERE source = 'carddav'")); |
| 426 |
3/3✓ Branch 199 → 200 taken 17 times.
✓ Branch 200 → 193 taken 10 times.
✓ Branch 200 → 201 taken 7 times.
|
17 | while (addresses.next()) |
| 427 |
3/3✓ Branch 193 → 194 taken 10 times.
✓ Branch 194 → 195 taken 10 times.
✓ Branch 195 → 196 taken 10 times.
|
10 | serverAddresses.insert(addresses.value(0).toString()); |
| 428 | 7 | } | |
| 429 | 7 | QStringList pending; | |
| 430 |
1/1✓ Branch 202 → 203 taken 7 times.
|
7 | QSqlQuery locals(m_db); |
| 431 |
1/1✓ Branch 206 → 207 taken 7 times.
|
7 | locals.exec(QStringLiteral("SELECT display_name, email, use_count, created_at, last_used FROM contacts " |
| 432 | "WHERE source = 'local' AND trim(email) <> '' ORDER BY id")); | ||
| 433 |
3/3✓ Branch 332 → 333 taken 14 times.
✓ Branch 333 → 210 taken 7 times.
✓ Branch 333 → 334 taken 7 times.
|
14 | while (locals.next()) { |
| 434 |
3/3✓ Branch 210 → 211 taken 7 times.
✓ Branch 211 → 212 taken 7 times.
✓ Branch 212 → 213 taken 7 times.
|
7 | const QString address = locals.value(1).toString().trimmed(); |
| 435 |
1/1✓ Branch 215 → 216 taken 7 times.
|
7 | const QString key = ContactIndex::addressKey(address); |
| 436 |
4/4✓ Branch 216 → 217 taken 7 times.
✓ Branch 217 → 218 taken 7 times.
✓ Branch 219 → 220 taken 3 times.
✓ Branch 219 → 288 taken 4 times.
|
7 | if (locals.value(2).toInt() > 0) { |
| 437 |
1/2✗ Branch 221 → 222 not taken.
✓ Branch 221 → 223 taken 3 times.
|
3 | if (serverAddresses.contains(key)) |
| 438 | ✗ | continue; // the usage count is enough | |
| 439 |
1/1✓ Branch 223 → 224 taken 3 times.
|
3 | QSqlQuery insert(m_db); |
| 440 |
1/1✓ Branch 227 → 228 taken 3 times.
|
3 | insert.prepare(QStringLiteral("INSERT OR IGNORE INTO collected_addresses " |
| 441 | "(address_key, address, display_name, first_used, last_used) VALUES (?, ?, ?, ?, ?)")); | ||
| 442 |
2/2✓ Branch 232 → 233 taken 3 times.
✓ Branch 233 → 234 taken 3 times.
|
3 | insert.addBindValue(sqlValue(key)); |
| 443 |
2/2✓ Branch 238 → 239 taken 3 times.
✓ Branch 239 → 240 taken 3 times.
|
3 | insert.addBindValue(sqlValue(address)); |
| 444 |
8/12✓ Branch 243 → 244 taken 3 times.
✓ Branch 244 → 245 taken 3 times.
✗ Branch 246 → 247 not taken.
✓ Branch 246 → 248 taken 3 times.
✓ Branch 248 → 249 taken 3 times.
✓ Branch 249 → 250 taken 3 times.
✓ Branch 251 → 252 taken 3 times.
✓ Branch 252 → 253 taken 3 times.
✓ Branch 256 → 257 taken 3 times.
✗ Branch 256 → 258 not taken.
✗ Branch 565 → 566 not taken.
✗ Branch 565 → 567 not taken.
|
3 | insert.addBindValue(sqlValue(locals.value(0).toString() == address ? QString() : locals.value(0).toString())); |
| 445 |
4/4✓ Branch 261 → 262 taken 3 times.
✓ Branch 262 → 263 taken 3 times.
✓ Branch 264 → 265 taken 3 times.
✓ Branch 265 → 266 taken 3 times.
|
3 | insert.addBindValue(sqlValue(locals.value(3).toString())); |
| 446 |
4/4✓ Branch 271 → 272 taken 3 times.
✓ Branch 272 → 273 taken 3 times.
✓ Branch 274 → 275 taken 3 times.
✓ Branch 275 → 276 taken 3 times.
|
3 | insert.addBindValue(sqlValue(locals.value(4).toString())); |
| 447 |
3/5✓ Branch 280 → 281 taken 3 times.
✓ Branch 281 → 282 taken 3 times.
✗ Branch 281 → 284 not taken.
✓ Branch 282 → 283 taken 3 times.
✗ Branch 282 → 284 not taken.
|
3 | ok = insert.exec() && ok; |
| 448 | 3 | continue; | |
| 449 | 3 | } | |
| 450 |
1/1✓ Branch 288 → 289 taken 4 times.
|
4 | ContactCard card; |
| 451 |
2/2✓ Branch 289 → 290 taken 4 times.
✓ Branch 290 → 291 taken 4 times.
|
4 | card.uid = QUuid::createUuid().toString(QUuid::WithoutBraces); |
| 452 |
6/10✓ Branch 293 → 294 taken 4 times.
✓ Branch 294 → 295 taken 4 times.
✗ Branch 296 → 297 not taken.
✓ Branch 296 → 298 taken 4 times.
✓ Branch 298 → 299 taken 4 times.
✓ Branch 299 → 300 taken 4 times.
✓ Branch 302 → 303 taken 4 times.
✗ Branch 302 → 304 not taken.
✗ Branch 610 → 611 not taken.
✗ Branch 610 → 612 not taken.
|
4 | card.formattedName = locals.value(0).toString() == address ? QString() : locals.value(0).toString(); |
| 453 | 4 | ContactValue value; | |
| 454 | 4 | value.value = address; | |
| 455 |
2/4✓ Branch 310 → 311 taken 4 times.
✓ Branch 310 → 312 taken 4 times.
✗ Branch 622 → 623 not taken.
✗ Branch 622 → 624 not taken.
|
8 | card.emails = {value}; |
| 456 |
1/1✓ Branch 312 → 313 taken 4 times.
|
4 | const qint64 id = addLocalCard(card); |
| 457 |
2/4✓ Branch 313 → 314 taken 4 times.
✗ Branch 313 → 316 not taken.
✓ Branch 314 → 315 taken 4 times.
✗ Branch 314 → 316 not taken.
|
4 | ok = id > 0 && ok; |
| 458 |
2/2✓ Branch 317 → 318 taken 4 times.
✓ Branch 318 → 319 taken 4 times.
|
4 | pending << QString::number(id); |
| 459 | 10 | } | |
| 460 |
2/2✓ Branch 336 → 337 taken 7 times.
✓ Branch 340 → 341 taken 7 times.
|
14 | setMeta(QStringLiteral("legacyLocalPending"), pending.join(QLatin1Char(','))); |
| 461 |
1/1✓ Branch 350 → 351 taken 7 times.
|
14 | setMeta(QStringLiteral("legacyTable"), QStringLiteral("present")); |
| 462 |
8/8✓ Branch 355 → 356 taken 7 times.
✓ Branch 359 → 360 taken 7 times.
✓ Branch 360 → 361 taken 7 times.
✓ Branch 362 → 363 taken 7 times.
✓ Branch 363 → 364 taken 7 times.
✓ Branch 365 → 366 taken 7 times.
✓ Branch 369 → 357 taken 7 times.
✓ Branch 369 → 370 taken 7 times.
|
14 | qCInfo(lcContactStore) << "Contact schema v1: legacy cards" << order.size() << "local-only cards" << pending.size(); |
| 463 | 7 | return ok; | |
| 464 | 11 | } | |
| 465 | |||
| 466 | // ─── address books ───────────────────────────────────────── | ||
| 467 | |||
| 468 | 17117 | qint64 ContactStore::ensureBook(const QString &accountId, const QString &path) { | |
| 469 |
3/3✓ Branch 2 → 3 taken 17117 times.
✓ Branch 4 → 5 taken 16478 times.
✓ Branch 4 → 7 taken 639 times.
|
17117 | if (const auto existing = book(accountId, path)) |
| 470 |
2/2✓ Branch 9 → 10 taken 639 times.
✓ Branch 9 → 12 taken 16478 times.
|
17117 | return existing->id; |
| 471 |
1/1✓ Branch 11 → 13 taken 639 times.
|
639 | QSqlQuery q(m_db); |
| 472 |
1/1✓ Branch 16 → 17 taken 639 times.
|
639 | q.prepare(QStringLiteral("INSERT INTO address_books (account_id, path) VALUES (?, ?)")); |
| 473 |
2/2✓ Branch 21 → 22 taken 639 times.
✓ Branch 22 → 23 taken 639 times.
|
639 | q.addBindValue(sqlValue(accountId)); |
| 474 |
2/2✓ Branch 27 → 28 taken 639 times.
✓ Branch 28 → 29 taken 639 times.
|
639 | q.addBindValue(sqlValue(path)); |
| 475 |
3/3✓ Branch 31 → 32 taken 639 times.
✓ Branch 32 → 33 taken 7 times.
✓ Branch 32 → 49 taken 632 times.
|
639 | if (!q.exec()) { |
| 476 |
8/8✓ Branch 33 → 34 taken 7 times.
✓ Branch 37 → 38 taken 7 times.
✓ Branch 38 → 39 taken 7 times.
✓ Branch 39 → 40 taken 7 times.
✓ Branch 40 → 41 taken 7 times.
✓ Branch 41 → 42 taken 7 times.
✓ Branch 47 → 35 taken 7 times.
✓ Branch 47 → 48 taken 7 times.
|
14 | qCWarning(lcContactStore) << "ensureBook failed:" << q.lastError().text(); |
| 477 | 7 | return 0; | |
| 478 | } | ||
| 479 |
2/2✓ Branch 49 → 50 taken 632 times.
✓ Branch 50 → 51 taken 632 times.
|
632 | return q.lastInsertId().toLongLong(); |
| 480 | 639 | } | |
| 481 | |||
| 482 |
1/1✓ Branch 6 → 7 taken 16655 times.
|
33310 | qint64 ContactStore::localBookId() { return ensureBook(QString(), QStringLiteral("local")); } |
| 483 | |||
| 484 | 5081 | QList<AddressBookInfo> ContactStore::books() const { | |
| 485 | 5081 | QList<AddressBookInfo> result; | |
| 486 |
1/1✓ Branch 2 → 3 taken 5081 times.
|
5081 | QSqlQuery q(m_db); |
| 487 |
1/1✓ Branch 9 → 10 taken 5081 times.
|
15243 | q.exec(QStringLiteral("SELECT %1 FROM address_books ORDER BY account_id, display_name, path") |
| 488 |
1/1✓ Branch 8 → 9 taken 5081 times.
|
10162 | .arg(QLatin1String(kBookColumns))); |
| 489 |
3/3✓ Branch 18 → 19 taken 11109 times.
✓ Branch 19 → 14 taken 6028 times.
✓ Branch 19 → 20 taken 5081 times.
|
11109 | while (q.next()) |
| 490 |
2/2✓ Branch 14 → 15 taken 6028 times.
✓ Branch 15 → 16 taken 6028 times.
|
6028 | result.append(bookFrom(q)); |
| 491 | 5081 | return result; | |
| 492 | 5081 | } | |
| 493 | |||
| 494 | 3304 | std::optional<AddressBookInfo> ContactStore::book(qint64 id) const { | |
| 495 |
1/1✓ Branch 2 → 3 taken 3304 times.
|
3304 | QSqlQuery q(m_db); |
| 496 |
2/2✓ Branch 8 → 9 taken 3304 times.
✓ Branch 9 → 10 taken 3304 times.
|
6608 | q.prepare(QStringLiteral("SELECT %1 FROM address_books WHERE id = ?").arg(QLatin1String(kBookColumns))); |
| 497 |
2/2✓ Branch 15 → 16 taken 3304 times.
✓ Branch 16 → 17 taken 3304 times.
|
3304 | q.addBindValue(sqlValue(id)); |
| 498 |
7/8✓ Branch 19 → 20 taken 3304 times.
✓ Branch 20 → 21 taken 3304 times.
✗ Branch 20 → 24 not taken.
✓ Branch 21 → 22 taken 3304 times.
✓ Branch 22 → 23 taken 2450 times.
✓ Branch 22 → 24 taken 854 times.
✓ Branch 25 → 26 taken 2450 times.
✓ Branch 25 → 30 taken 854 times.
|
3304 | if (q.exec() && q.next()) |
| 499 |
1/1✓ Branch 26 → 27 taken 2450 times.
|
2450 | return bookFrom(q); |
| 500 | 854 | return std::nullopt; | |
| 501 | 3304 | } | |
| 502 | |||
| 503 | 20118 | std::optional<AddressBookInfo> ContactStore::book(const QString &accountId, const QString &path) const { | |
| 504 |
1/1✓ Branch 2 → 3 taken 20118 times.
|
20118 | QSqlQuery q(m_db); |
| 505 |
1/1✓ Branch 9 → 10 taken 20118 times.
|
60354 | q.prepare(QStringLiteral("SELECT %1 FROM address_books WHERE account_id = ? AND path = ?") |
| 506 |
1/1✓ Branch 8 → 9 taken 20118 times.
|
60354 | .arg(QLatin1String(kBookColumns))); |
| 507 |
2/2✓ Branch 15 → 16 taken 20118 times.
✓ Branch 16 → 17 taken 20118 times.
|
20118 | q.addBindValue(sqlValue(accountId)); |
| 508 |
2/2✓ Branch 21 → 22 taken 20118 times.
✓ Branch 22 → 23 taken 20118 times.
|
20118 | q.addBindValue(sqlValue(path)); |
| 509 |
8/8✓ Branch 25 → 26 taken 20118 times.
✓ Branch 26 → 27 taken 20111 times.
✓ Branch 26 → 30 taken 7 times.
✓ Branch 27 → 28 taken 20111 times.
✓ Branch 28 → 29 taken 16955 times.
✓ Branch 28 → 30 taken 3156 times.
✓ Branch 31 → 32 taken 16955 times.
✓ Branch 31 → 36 taken 3163 times.
|
20118 | if (q.exec() && q.next()) |
| 510 |
1/1✓ Branch 32 → 33 taken 16955 times.
|
16955 | return bookFrom(q); |
| 511 | 3163 | return std::nullopt; | |
| 512 | 20118 | } | |
| 513 | |||
| 514 | 368 | bool ContactStore::updateBook(const AddressBookInfo &book) { | |
| 515 |
1/1✓ Branch 2 → 3 taken 368 times.
|
368 | QSqlQuery q(m_db); |
| 516 |
1/1✓ Branch 6 → 7 taken 368 times.
|
368 | q.prepare(QStringLiteral( |
| 517 | "UPDATE address_books SET display_name = ?, read_only = ?, ctag = ?, sync_token = ?, " | ||
| 518 | "supports_sync_collection = ?, supported_versions = ?, max_resource_size = ?, fetched = ?, " | ||
| 519 | "last_sync = ?, last_error = ?, skipped_cards = ? WHERE id = ?")); | ||
| 520 |
2/2✓ Branch 11 → 12 taken 368 times.
✓ Branch 12 → 13 taken 368 times.
|
368 | q.addBindValue(sqlValue(book.displayName)); |
| 521 |
2/2✓ Branch 17 → 18 taken 368 times.
✓ Branch 18 → 19 taken 368 times.
|
368 | q.addBindValue(sqlValue(book.readOnly)); |
| 522 |
2/2✓ Branch 23 → 24 taken 368 times.
✓ Branch 24 → 25 taken 368 times.
|
368 | q.addBindValue(sqlValue(book.ctag)); |
| 523 |
2/2✓ Branch 29 → 30 taken 368 times.
✓ Branch 30 → 31 taken 368 times.
|
368 | q.addBindValue(sqlValue(book.syncToken)); |
| 524 |
2/2✓ Branch 35 → 36 taken 368 times.
✓ Branch 36 → 37 taken 368 times.
|
368 | q.addBindValue(sqlValue(book.supportsSyncCollection)); |
| 525 |
3/3✓ Branch 42 → 43 taken 368 times.
✓ Branch 44 → 45 taken 368 times.
✓ Branch 45 → 46 taken 368 times.
|
368 | q.addBindValue(sqlValue(book.supportedVersions.join(QLatin1Char(',')))); |
| 526 |
2/2✓ Branch 51 → 52 taken 368 times.
✓ Branch 52 → 53 taken 368 times.
|
368 | q.addBindValue(sqlValue(book.maxResourceSize)); |
| 527 |
2/2✓ Branch 57 → 58 taken 368 times.
✓ Branch 58 → 59 taken 368 times.
|
368 | q.addBindValue(sqlValue(book.fetched)); |
| 528 |
6/6✓ Branch 62 → 63 taken 368 times.
✓ Branch 63 → 64 taken 199 times.
✓ Branch 63 → 65 taken 169 times.
✓ Branch 64 → 66 taken 199 times.
✓ Branch 67 → 68 taken 368 times.
✓ Branch 68 → 69 taken 368 times.
|
368 | q.addBindValue(sqlValue(book.lastSync.isValid() ? book.lastSync.toString(Qt::ISODate) : QString())); |
| 529 |
2/2✓ Branch 74 → 75 taken 368 times.
✓ Branch 75 → 76 taken 368 times.
|
368 | q.addBindValue(sqlValue(book.lastError)); |
| 530 |
2/2✓ Branch 80 → 81 taken 368 times.
✓ Branch 81 → 82 taken 368 times.
|
368 | q.addBindValue(sqlValue(book.skippedCards)); |
| 531 |
2/2✓ Branch 86 → 87 taken 368 times.
✓ Branch 87 → 88 taken 368 times.
|
368 | q.addBindValue(sqlValue(book.id)); |
| 532 |
1/1✓ Branch 90 → 91 taken 368 times.
|
736 | return q.exec(); |
| 533 | 368 | } | |
| 534 | |||
| 535 | 4 | bool ContactStore::setBookFlags(qint64 id, bool showInAll, bool useForCompletion) { | |
| 536 |
1/1✓ Branch 2 → 3 taken 4 times.
|
4 | QSqlQuery q(m_db); |
| 537 |
1/1✓ Branch 6 → 7 taken 4 times.
|
4 | q.prepare(QStringLiteral("UPDATE address_books SET show_in_all = ?, use_for_completion = ? WHERE id = ?")); |
| 538 |
2/2✓ Branch 11 → 12 taken 4 times.
✓ Branch 12 → 13 taken 4 times.
|
4 | q.addBindValue(sqlValue(showInAll)); |
| 539 |
2/2✓ Branch 17 → 18 taken 4 times.
✓ Branch 18 → 19 taken 4 times.
|
4 | q.addBindValue(sqlValue(useForCompletion)); |
| 540 |
2/2✓ Branch 23 → 24 taken 4 times.
✓ Branch 24 → 25 taken 4 times.
|
4 | q.addBindValue(sqlValue(id)); |
| 541 |
5/6✓ Branch 27 → 28 taken 4 times.
✓ Branch 28 → 29 taken 4 times.
✗ Branch 28 → 32 not taken.
✓ Branch 29 → 30 taken 4 times.
✓ Branch 30 → 31 taken 3 times.
✓ Branch 30 → 32 taken 1 time.
|
8 | return q.exec() && q.numRowsAffected() == 1; // an unknown book is no success |
| 542 | 4 | } | |
| 543 | |||
| 544 | 15 | bool ContactStore::removeBook(qint64 id) { | |
| 545 |
1/1✓ Branch 3 → 4 taken 15 times.
|
30 | return inSavepoint([&] { |
| 546 |
1/1✓ Branch 2 → 3 taken 15 times.
|
15 | QSqlQuery q(m_db); |
| 547 |
1/1✓ Branch 6 → 7 taken 15 times.
|
15 | q.prepare(QStringLiteral("DELETE FROM address_books WHERE id = ?")); |
| 548 |
2/2✓ Branch 11 → 12 taken 15 times.
✓ Branch 12 → 13 taken 15 times.
|
15 | q.addBindValue(sqlValue(id)); |
| 549 |
1/1✓ Branch 15 → 16 taken 15 times.
|
15 | QSqlQuery outbox(m_db); |
| 550 |
1/1✓ Branch 19 → 20 taken 15 times.
|
15 | outbox.prepare(QStringLiteral("DELETE FROM contact_outbox WHERE book_id = ?")); |
| 551 |
2/2✓ Branch 24 → 25 taken 15 times.
✓ Branch 25 → 26 taken 15 times.
|
15 | outbox.addBindValue(sqlValue(id)); |
| 552 |
5/6✓ Branch 28 → 29 taken 15 times.
✓ Branch 29 → 30 taken 15 times.
✗ Branch 29 → 33 not taken.
✓ Branch 30 → 31 taken 15 times.
✓ Branch 31 → 32 taken 14 times.
✓ Branch 31 → 33 taken 1 time.
|
30 | return q.exec() && outbox.exec(); |
| 553 | 45 | }); | |
| 554 | } | ||
| 555 | |||
| 556 | // ─── cards ───────────────────────────────────────────────── | ||
| 557 | |||
| 558 | 2888 | std::optional<StoredCard> ContactStore::card(qint64 bookId, const QString &uid) const { | |
| 559 |
1/1✓ Branch 2 → 3 taken 2888 times.
|
2888 | QSqlQuery q(m_db); |
| 560 |
2/2✓ Branch 8 → 9 taken 2888 times.
✓ Branch 9 → 10 taken 2888 times.
|
5776 | q.prepare(QStringLiteral("SELECT %1 FROM cards WHERE book_id = ? AND uid = ?").arg(QLatin1String(kCardColumns))); |
| 561 |
2/2✓ Branch 15 → 16 taken 2888 times.
✓ Branch 16 → 17 taken 2888 times.
|
2888 | q.addBindValue(sqlValue(bookId)); |
| 562 |
2/2✓ Branch 21 → 22 taken 2888 times.
✓ Branch 22 → 23 taken 2888 times.
|
2888 | q.addBindValue(sqlValue(uid)); |
| 563 |
8/8✓ Branch 25 → 26 taken 2888 times.
✓ Branch 26 → 27 taken 2887 times.
✓ Branch 26 → 30 taken 1 time.
✓ Branch 27 → 28 taken 2887 times.
✓ Branch 28 → 29 taken 2767 times.
✓ Branch 28 → 30 taken 120 times.
✓ Branch 31 → 32 taken 2767 times.
✓ Branch 31 → 36 taken 121 times.
|
2888 | if (q.exec() && q.next()) |
| 564 |
1/1✓ Branch 32 → 33 taken 2767 times.
|
2767 | return cardFrom(q); |
| 565 | 121 | return std::nullopt; | |
| 566 | 2888 | } | |
| 567 | |||
| 568 | 4 | QList<StoredCard> ContactStore::cards(qint64 bookId) const { | |
| 569 | 4 | QList<StoredCard> result; | |
| 570 |
1/1✓ Branch 2 → 3 taken 4 times.
|
4 | QSqlQuery q(m_db); |
| 571 |
2/2✓ Branch 8 → 9 taken 4 times.
✓ Branch 9 → 10 taken 4 times.
|
8 | q.prepare(QStringLiteral("SELECT %1 FROM cards WHERE book_id = ? ORDER BY id").arg(QLatin1String(kCardColumns))); |
| 572 |
1/1✓ Branch 15 → 16 taken 4 times.
|
4 | q.addBindValue(bookId); |
| 573 |
2/3✓ Branch 17 → 18 taken 4 times.
✓ Branch 18 → 19 taken 4 times.
✗ Branch 18 → 26 not taken.
|
4 | if (q.exec()) |
| 574 |
3/3✓ Branch 24 → 25 taken 11 times.
✓ Branch 25 → 20 taken 7 times.
✓ Branch 25 → 26 taken 4 times.
|
11 | while (q.next()) |
| 575 |
2/2✓ Branch 20 → 21 taken 7 times.
✓ Branch 21 → 22 taken 7 times.
|
7 | result.append(cardFrom(q)); |
| 576 | 4 | return result; | |
| 577 | 4 | } | |
| 578 | |||
| 579 | 8 | std::optional<StoredCard> ContactStore::cardById(qint64 id) const { | |
| 580 |
1/1✓ Branch 2 → 3 taken 8 times.
|
8 | QSqlQuery q(m_db); |
| 581 |
2/2✓ Branch 8 → 9 taken 8 times.
✓ Branch 9 → 10 taken 8 times.
|
16 | q.prepare(QStringLiteral("SELECT %1 FROM cards WHERE id = ?").arg(QLatin1String(kCardColumns))); |
| 582 |
2/2✓ Branch 15 → 16 taken 8 times.
✓ Branch 16 → 17 taken 8 times.
|
8 | q.addBindValue(sqlValue(id)); |
| 583 |
7/8✓ Branch 19 → 20 taken 8 times.
✓ Branch 20 → 21 taken 7 times.
✓ Branch 20 → 24 taken 1 time.
✓ Branch 21 → 22 taken 7 times.
✓ Branch 22 → 23 taken 7 times.
✗ Branch 22 → 24 not taken.
✓ Branch 25 → 26 taken 7 times.
✓ Branch 25 → 30 taken 1 time.
|
8 | if (q.exec() && q.next()) |
| 584 |
1/1✓ Branch 26 → 27 taken 7 times.
|
7 | return cardFrom(q); |
| 585 | 1 | return std::nullopt; | |
| 586 | 8 | } | |
| 587 | |||
| 588 | 5308 | std::optional<StoredCard> ContactStore::cardByHref(qint64 bookId, const QString &href) const { | |
| 589 |
1/1✓ Branch 2 → 3 taken 5308 times.
|
5308 | QSqlQuery q(m_db); |
| 590 |
1/1✓ Branch 9 → 10 taken 5308 times.
|
15924 | q.prepare(QStringLiteral("SELECT %1 FROM cards WHERE book_id = ? AND href = ? AND sync_state <> 'legacy'") |
| 591 |
1/1✓ Branch 8 → 9 taken 5308 times.
|
15924 | .arg(QLatin1String(kCardColumns))); |
| 592 |
2/2✓ Branch 15 → 16 taken 5308 times.
✓ Branch 16 → 17 taken 5308 times.
|
5308 | q.addBindValue(sqlValue(bookId)); |
| 593 |
2/2✓ Branch 21 → 22 taken 5308 times.
✓ Branch 22 → 23 taken 5308 times.
|
5308 | q.addBindValue(sqlValue(href)); |
| 594 |
7/8✓ Branch 25 → 26 taken 5308 times.
✓ Branch 26 → 27 taken 5308 times.
✗ Branch 26 → 30 not taken.
✓ Branch 27 → 28 taken 5308 times.
✓ Branch 28 → 29 taken 10 times.
✓ Branch 28 → 30 taken 5298 times.
✓ Branch 31 → 32 taken 10 times.
✓ Branch 31 → 36 taken 5298 times.
|
5308 | if (q.exec() && q.next()) |
| 595 |
1/1✓ Branch 32 → 33 taken 10 times.
|
10 | return cardFrom(q); |
| 596 | 5298 | return std::nullopt; | |
| 597 | 5308 | } | |
| 598 | |||
| 599 | 5308 | QString ContactStore::uidForHref(qint64 bookId, const QString &href) const { | |
| 600 |
1/1✓ Branch 2 → 3 taken 5308 times.
|
5308 | const auto found = cardByHref(bookId, href); |
| 601 |
2/2✓ Branch 4 → 5 taken 10 times.
✓ Branch 4 → 7 taken 5298 times.
|
10616 | return found ? found->uid : QString(); |
| 602 | 5308 | } | |
| 603 | |||
| 604 | 51 | QHash<QString, QString> ContactStore::cardEtags(qint64 bookId) const { | |
| 605 | 51 | QHash<QString, QString> result; | |
| 606 |
1/1✓ Branch 2 → 3 taken 51 times.
|
51 | QSqlQuery q(m_db); |
| 607 |
1/1✓ Branch 6 → 7 taken 51 times.
|
51 | q.prepare(QStringLiteral("SELECT href, etag FROM cards WHERE book_id = ? AND href <> '' AND sync_state <> 'legacy'")); |
| 608 |
2/2✓ Branch 11 → 12 taken 51 times.
✓ Branch 12 → 13 taken 51 times.
|
51 | q.addBindValue(sqlValue(bookId)); |
| 609 |
3/3✓ Branch 15 → 16 taken 51 times.
✓ Branch 16 → 17 taken 50 times.
✓ Branch 16 → 30 taken 1 time.
|
51 | if (q.exec()) |
| 610 |
3/3✓ Branch 28 → 29 taken 346 times.
✓ Branch 29 → 18 taken 296 times.
✓ Branch 29 → 30 taken 50 times.
|
346 | while (q.next()) |
| 611 |
5/5✓ Branch 18 → 19 taken 296 times.
✓ Branch 19 → 20 taken 296 times.
✓ Branch 20 → 21 taken 296 times.
✓ Branch 21 → 22 taken 296 times.
✓ Branch 22 → 23 taken 296 times.
|
296 | result.insert(q.value(0).toString(), q.value(1).toString()); |
| 612 | 51 | return result; | |
| 613 | 51 | } | |
| 614 | |||
| 615 | // A SQLite savepoint nests inside a transaction the caller holds (sync | ||
| 616 | // pages) and is its own transaction otherwise; a failed write rolls back | ||
| 617 | // to it. S106-154/167: the card row and its ~15 derived-index statements, | ||
| 618 | // and a book with its outbox, change together or not at all. | ||
| 619 | 30517 | bool ContactStore::inSavepoint(const std::function<bool()> &write) { | |
| 620 |
1/1✓ Branch 2 → 3 taken 30517 times.
|
30517 | QSqlQuery q(m_db); |
| 621 |
2/3✓ Branch 6 → 7 taken 30517 times.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 26 taken 30517 times.
|
30517 | if (!q.exec(QStringLiteral("SAVEPOINT contact_write"))) { |
| 622 | ✗ | qCWarning(lcContactStore) << "savepoint:" << q.lastError().text(); | |
| 623 | ✗ | return false; | |
| 624 | } | ||
| 625 |
11/16✓ Branch 26 → 27 taken 30517 times.
✓ Branch 27 → 28 taken 30511 times.
✓ Branch 27 → 34 taken 6 times.
✓ Branch 31 → 32 taken 30511 times.
✓ Branch 32 → 33 taken 30511 times.
✗ Branch 32 → 34 not taken.
✓ Branch 35 → 36 taken 30511 times.
✓ Branch 35 → 37 taken 6 times.
✓ Branch 37 → 38 taken 30511 times.
✓ Branch 37 → 39 taken 6 times.
✓ Branch 39 → 40 taken 30511 times.
✓ Branch 39 → 41 taken 6 times.
✗ Branch 74 → 75 not taken.
✗ Branch 74 → 76 not taken.
✗ Branch 78 → 79 not taken.
✗ Branch 78 → 80 not taken.
|
61028 | if (write() && q.exec(QStringLiteral("RELEASE contact_write"))) |
| 626 | 30511 | return true; | |
| 627 |
1/1✓ Branch 44 → 45 taken 6 times.
|
6 | q.exec(QStringLiteral("ROLLBACK TO contact_write")); |
| 628 |
1/1✓ Branch 50 → 51 taken 6 times.
|
6 | q.exec(QStringLiteral("RELEASE contact_write")); |
| 629 | 6 | return false; | |
| 630 | 30517 | } | |
| 631 | |||
| 632 | 30502 | qint64 ContactStore::putCard(const StoredCard &card) { | |
| 633 | 30502 | qint64 id = 0; | |
| 634 |
2/2✓ Branch 2 → 3 taken 30502 times.
✓ Branch 3 → 4 taken 30502 times.
|
61004 | inSavepoint([&] { id = writeCard(card); return id > 0; }); |
| 635 | 30502 | return id; | |
| 636 | } | ||
| 637 | |||
| 638 | 30502 | qint64 ContactStore::writeCard(const StoredCard &card) { | |
| 639 |
5/6✓ Branch 2 → 3 taken 30501 times.
✓ Branch 2 → 5 taken 1 time.
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 30501 times.
✓ Branch 7 → 8 taken 1 time.
✓ Branch 7 → 9 taken 30501 times.
|
30502 | if (card.bookId <= 0 || card.uid.isEmpty()) |
| 640 | 1 | return 0; | |
| 641 |
1/1✓ Branch 9 → 10 taken 30501 times.
|
30501 | QSqlQuery update(m_db); |
| 642 |
1/1✓ Branch 13 → 14 taken 30501 times.
|
30501 | update.prepare(QStringLiteral( |
| 643 | "UPDATE cards SET href = ?, etag = ?, raw = ?, base_raw = ?, sync_state = ?, writable = ?, " | ||
| 644 | "updated_at = datetime('now') WHERE book_id = ? AND uid = ?")); | ||
| 645 |
2/2✓ Branch 18 → 19 taken 30501 times.
✓ Branch 19 → 20 taken 30501 times.
|
30501 | update.addBindValue(sqlValue(card.href)); |
| 646 |
2/2✓ Branch 24 → 25 taken 30501 times.
✓ Branch 25 → 26 taken 30501 times.
|
30501 | update.addBindValue(sqlValue(card.etag)); |
| 647 |
2/2✓ Branch 30 → 31 taken 30501 times.
✓ Branch 31 → 32 taken 30501 times.
|
30501 | update.addBindValue(sqlValue(card.raw)); |
| 648 |
2/2✓ Branch 36 → 37 taken 30501 times.
✓ Branch 37 → 38 taken 30501 times.
|
30501 | update.addBindValue(sqlValue(card.baseRaw)); |
| 649 |
2/2✓ Branch 42 → 43 taken 30501 times.
✓ Branch 43 → 44 taken 30501 times.
|
30501 | update.addBindValue(sqlValue(card.syncState)); |
| 650 |
2/2✓ Branch 48 → 49 taken 30501 times.
✓ Branch 49 → 50 taken 30501 times.
|
30501 | update.addBindValue(sqlValue(card.writable)); |
| 651 |
2/2✓ Branch 54 → 55 taken 30501 times.
✓ Branch 55 → 56 taken 30501 times.
|
30501 | update.addBindValue(sqlValue(card.bookId)); |
| 652 |
2/2✓ Branch 60 → 61 taken 30501 times.
✓ Branch 61 → 62 taken 30501 times.
|
30501 | update.addBindValue(sqlValue(card.uid)); |
| 653 |
3/3✓ Branch 64 → 65 taken 30501 times.
✓ Branch 65 → 66 taken 1 time.
✓ Branch 65 → 82 taken 30500 times.
|
30501 | if (!update.exec()) { |
| 654 |
8/8✓ Branch 66 → 67 taken 1 time.
✓ Branch 70 → 71 taken 1 time.
✓ Branch 71 → 72 taken 1 time.
✓ Branch 72 → 73 taken 1 time.
✓ Branch 73 → 74 taken 1 time.
✓ Branch 74 → 75 taken 1 time.
✓ Branch 80 → 68 taken 1 time.
✓ Branch 80 → 81 taken 1 time.
|
2 | qCWarning(lcContactStore) << "putCard failed:" << update.lastError().text(); |
| 655 | 1 | return 0; | |
| 656 | } | ||
| 657 | 30500 | qint64 id = 0; | |
| 658 |
3/3✓ Branch 82 → 83 taken 30500 times.
✓ Branch 83 → 84 taken 284 times.
✓ Branch 83 → 90 taken 30216 times.
|
30500 | if (update.numRowsAffected() > 0) { |
| 659 |
1/1✓ Branch 84 → 85 taken 284 times.
|
284 | id = this->card(card.bookId, card.uid).value_or(StoredCard{}).id; |
| 660 | } else { | ||
| 661 |
1/1✓ Branch 90 → 91 taken 30216 times.
|
30216 | QSqlQuery insert(m_db); |
| 662 |
1/1✓ Branch 94 → 95 taken 30216 times.
|
30216 | insert.prepare(QStringLiteral("INSERT INTO cards (book_id, uid, href, etag, raw, base_raw, sync_state, writable) " |
| 663 | "VALUES (?, ?, ?, ?, ?, ?, ?, ?)")); | ||
| 664 |
2/2✓ Branch 99 → 100 taken 30216 times.
✓ Branch 100 → 101 taken 30216 times.
|
30216 | insert.addBindValue(sqlValue(card.bookId)); |
| 665 |
2/2✓ Branch 105 → 106 taken 30216 times.
✓ Branch 106 → 107 taken 30216 times.
|
30216 | insert.addBindValue(sqlValue(card.uid)); |
| 666 |
2/2✓ Branch 111 → 112 taken 30216 times.
✓ Branch 112 → 113 taken 30216 times.
|
30216 | insert.addBindValue(sqlValue(card.href)); |
| 667 |
2/2✓ Branch 117 → 118 taken 30216 times.
✓ Branch 118 → 119 taken 30216 times.
|
30216 | insert.addBindValue(sqlValue(card.etag)); |
| 668 |
2/2✓ Branch 123 → 124 taken 30216 times.
✓ Branch 124 → 125 taken 30216 times.
|
30216 | insert.addBindValue(sqlValue(card.raw)); |
| 669 |
2/2✓ Branch 129 → 130 taken 30216 times.
✓ Branch 130 → 131 taken 30216 times.
|
30216 | insert.addBindValue(sqlValue(card.baseRaw)); |
| 670 |
2/2✓ Branch 135 → 136 taken 30216 times.
✓ Branch 136 → 137 taken 30216 times.
|
30216 | insert.addBindValue(sqlValue(card.syncState)); |
| 671 |
2/2✓ Branch 141 → 142 taken 30216 times.
✓ Branch 142 → 143 taken 30216 times.
|
30216 | insert.addBindValue(sqlValue(card.writable)); |
| 672 |
3/3✓ Branch 145 → 146 taken 30216 times.
✓ Branch 146 → 147 taken 2 times.
✓ Branch 146 → 163 taken 30214 times.
|
30216 | if (!insert.exec()) { |
| 673 |
8/8✓ Branch 147 → 148 taken 2 times.
✓ Branch 151 → 152 taken 2 times.
✓ Branch 152 → 153 taken 2 times.
✓ Branch 153 → 154 taken 2 times.
✓ Branch 154 → 155 taken 2 times.
✓ Branch 155 → 156 taken 2 times.
✓ Branch 161 → 149 taken 2 times.
✓ Branch 161 → 162 taken 2 times.
|
4 | qCWarning(lcContactStore) << "putCard insert failed:" << insert.lastError().text(); |
| 674 | 2 | return 0; | |
| 675 | } | ||
| 676 |
2/2✓ Branch 163 → 164 taken 30214 times.
✓ Branch 164 → 165 taken 30214 times.
|
30214 | id = insert.lastInsertId().toLongLong(); |
| 677 | 30216 | } | |
| 678 |
3/3✓ Branch 172 → 173 taken 30498 times.
✓ Branch 173 → 174 taken 30497 times.
✓ Branch 173 → 175 taken 1 time.
|
30498 | return writeDerived(id, card.raw) ? id : 0; |
| 679 | 30501 | } | |
| 680 | |||
| 681 | 30498 | bool ContactStore::writeDerived(qint64 cardId, const QString &raw) { | |
| 682 |
1/1✓ Branch 2 → 3 taken 30498 times.
|
30498 | ContactCard card; |
| 683 | 30498 | bool writable = true; | |
| 684 |
1/1✓ Branch 3 → 4 taken 30498 times.
|
30498 | const bool readable = VCardCodec::parse(raw, &card, nullptr, &writable); |
| 685 |
1/1✓ Branch 4 → 5 taken 30498 times.
|
30498 | const QString name = ContactIndex::displayName(card); |
| 686 |
1/1✓ Branch 5 → 6 taken 30498 times.
|
30498 | const QString folded = ContactIndex::foldForSort(name); |
| 687 |
1/1✓ Branch 6 → 7 taken 30498 times.
|
30498 | QSqlQuery q(m_db); |
| 688 |
1/1✓ Branch 10 → 11 taken 30498 times.
|
30498 | q.prepare(QStringLiteral( |
| 689 | "UPDATE cards SET version = ?, kind = ?, display_name = ?, sort_display = ?, sort_family = ?, section = ?, " | ||
| 690 | "organization = ?, primary_address = ?, address_count = ?, has_photo = ?, search_text = ?, birthday = ?, " | ||
| 691 | "photo_uri = ?, phone = ?, job_title = ?, locality = ?, anniversary = ?, writable = writable AND ? WHERE id = ?")); | ||
| 692 |
2/2✓ Branch 15 → 16 taken 30498 times.
✓ Branch 16 → 17 taken 30498 times.
|
30498 | q.addBindValue(sqlValue(card.version)); |
| 693 |
2/2✓ Branch 21 → 22 taken 30498 times.
✓ Branch 22 → 23 taken 30498 times.
|
30498 | q.addBindValue(sqlValue(card.kind)); |
| 694 |
2/2✓ Branch 27 → 28 taken 30498 times.
✓ Branch 28 → 29 taken 30498 times.
|
30498 | q.addBindValue(sqlValue(name)); |
| 695 |
2/2✓ Branch 33 → 34 taken 30498 times.
✓ Branch 34 → 35 taken 30498 times.
|
30498 | q.addBindValue(sqlValue(folded)); |
| 696 |
4/4✓ Branch 38 → 39 taken 30498 times.
✓ Branch 39 → 40 taken 30498 times.
✓ Branch 41 → 42 taken 30498 times.
✓ Branch 42 → 43 taken 30498 times.
|
30498 | q.addBindValue(sqlValue(ContactIndex::foldForSort(ContactIndex::familyFirstName(card)))); |
| 697 |
3/3✓ Branch 48 → 49 taken 30498 times.
✓ Branch 50 → 51 taken 30498 times.
✓ Branch 51 → 52 taken 30498 times.
|
30498 | q.addBindValue(sqlValue(ContactIndex::sectionOf(folded))); |
| 698 |
2/2✓ Branch 57 → 58 taken 30498 times.
✓ Branch 58 → 59 taken 30498 times.
|
30498 | q.addBindValue(sqlValue(card.organization)); |
| 699 |
3/3✓ Branch 62 → 63 taken 30498 times.
✓ Branch 64 → 65 taken 30498 times.
✓ Branch 65 → 66 taken 30498 times.
|
30498 | q.addBindValue(sqlValue(ContactIndex::primaryAddress(card))); |
| 700 |
2/2✓ Branch 72 → 73 taken 30498 times.
✓ Branch 73 → 74 taken 30498 times.
|
30498 | q.addBindValue(sqlValue(int(card.emails.size()))); |
| 701 |
2/2✓ Branch 78 → 79 taken 30498 times.
✓ Branch 79 → 80 taken 30498 times.
|
30498 | q.addBindValue(sqlValue(card.photo.kind != ContactPhoto::Kind::None)); |
| 702 |
3/3✓ Branch 83 → 84 taken 30498 times.
✓ Branch 85 → 86 taken 30498 times.
✓ Branch 86 → 87 taken 30498 times.
|
30498 | q.addBindValue(sqlValue(ContactIndex::searchText(card))); |
| 703 |
5/5✓ Branch 92 → 93 taken 340 times.
✓ Branch 92 → 94 taken 30158 times.
✓ Branch 93 → 95 taken 340 times.
✓ Branch 96 → 97 taken 30498 times.
✓ Branch 97 → 98 taken 30498 times.
|
30498 | q.addBindValue(sqlValue(card.birthday.isValid() ? card.birthday.toIso() : QString())); |
| 704 |
4/4✓ Branch 102 → 103 taken 5 times.
✓ Branch 102 → 104 taken 30493 times.
✓ Branch 106 → 107 taken 30498 times.
✓ Branch 107 → 108 taken 30498 times.
|
30498 | q.addBindValue(sqlValue(card.photo.kind == ContactPhoto::Kind::Uri ? card.photo.uri : QString())); |
| 705 |
3/3✓ Branch 112 → 113 taken 30498 times.
✓ Branch 114 → 115 taken 30498 times.
✓ Branch 115 → 116 taken 30498 times.
|
30498 | q.addBindValue(sqlValue(ContactIndex::primaryPhone(card))); |
| 706 |
3/3✓ Branch 120 → 121 taken 30498 times.
✓ Branch 122 → 123 taken 30498 times.
✓ Branch 123 → 124 taken 30498 times.
|
30498 | q.addBindValue(sqlValue(card.title.trimmed())); |
| 707 |
3/3✓ Branch 128 → 129 taken 30498 times.
✓ Branch 130 → 131 taken 30498 times.
✓ Branch 131 → 132 taken 30498 times.
|
30498 | q.addBindValue(sqlValue(ContactIndex::primaryLocality(card))); |
| 708 |
5/5✓ Branch 137 → 138 taken 51 times.
✓ Branch 137 → 139 taken 30447 times.
✓ Branch 138 → 140 taken 51 times.
✓ Branch 141 → 142 taken 30498 times.
✓ Branch 142 → 143 taken 30498 times.
|
30498 | q.addBindValue(sqlValue(card.anniversary.isValid() ? card.anniversary.toIso() : QString())); |
| 709 |
5/6✓ Branch 147 → 148 taken 30498 times.
✗ Branch 147 → 150 not taken.
✓ Branch 148 → 149 taken 30497 times.
✓ Branch 148 → 150 taken 1 time.
✓ Branch 152 → 153 taken 30498 times.
✓ Branch 153 → 154 taken 30498 times.
|
30498 | q.addBindValue(sqlValue(readable && writable)); |
| 710 |
2/2✓ Branch 158 → 159 taken 30498 times.
✓ Branch 159 → 160 taken 30498 times.
|
30498 | q.addBindValue(sqlValue(cardId)); |
| 711 |
1/1✓ Branch 162 → 163 taken 30498 times.
|
30498 | bool ok = q.exec(); |
| 712 |
2/2✓ Branch 189 → 165 taken 91494 times.
✓ Branch 189 → 190 taken 30498 times.
|
121992 | for (const char *table : {"card_addresses", "card_groups", "card_members"}) { |
| 713 |
1/1✓ Branch 165 → 166 taken 91494 times.
|
91494 | QSqlQuery clear(m_db); |
| 714 |
2/2✓ Branch 171 → 172 taken 91494 times.
✓ Branch 172 → 173 taken 91494 times.
|
182988 | clear.prepare(QStringLiteral("DELETE FROM %1 WHERE card_id = ?").arg(QLatin1String(table))); |
| 715 |
2/2✓ Branch 178 → 179 taken 91494 times.
✓ Branch 179 → 180 taken 91494 times.
|
91494 | clear.addBindValue(sqlValue(cardId)); |
| 716 |
3/5✓ Branch 182 → 183 taken 91494 times.
✓ Branch 183 → 184 taken 91494 times.
✗ Branch 183 → 186 not taken.
✓ Branch 184 → 185 taken 91494 times.
✗ Branch 184 → 186 not taken.
|
91494 | ok = clear.exec() && ok; |
| 717 | 91494 | } | |
| 718 |
2/2✓ Branch 257 → 191 taken 29937 times.
✓ Branch 257 → 258 taken 30498 times.
|
60435 | for (int i = 0; i < card.emails.size(); ++i) { |
| 719 | 29937 | const ContactValue &email = card.emails.at(i); | |
| 720 |
2/3✓ Branch 192 → 193 taken 29937 times.
✗ Branch 195 → 196 not taken.
✓ Branch 195 → 197 taken 29937 times.
|
29937 | if (email.value.trimmed().isEmpty()) |
| 721 | ✗ | continue; | |
| 722 |
1/1✓ Branch 197 → 198 taken 29937 times.
|
29937 | QSqlQuery insert(m_db); |
| 723 |
1/1✓ Branch 201 → 202 taken 29937 times.
|
29937 | insert.prepare(QStringLiteral("INSERT INTO card_addresses (card_id, position, address, address_key, type, preferred) " |
| 724 | "VALUES (?, ?, ?, ?, ?, ?)")); | ||
| 725 |
2/2✓ Branch 206 → 207 taken 29937 times.
✓ Branch 207 → 208 taken 29937 times.
|
29937 | insert.addBindValue(sqlValue(cardId)); |
| 726 |
2/2✓ Branch 212 → 213 taken 29937 times.
✓ Branch 213 → 214 taken 29937 times.
|
29937 | insert.addBindValue(sqlValue(i)); |
| 727 |
3/3✓ Branch 217 → 218 taken 29937 times.
✓ Branch 219 → 220 taken 29937 times.
✓ Branch 220 → 221 taken 29937 times.
|
29937 | insert.addBindValue(sqlValue(email.value.trimmed())); |
| 728 |
3/3✓ Branch 225 → 226 taken 29937 times.
✓ Branch 227 → 228 taken 29937 times.
✓ Branch 228 → 229 taken 29937 times.
|
29937 | insert.addBindValue(sqlValue(ContactIndex::addressKey(email.value))); |
| 729 |
3/3✓ Branch 235 → 236 taken 29937 times.
✓ Branch 237 → 238 taken 29937 times.
✓ Branch 238 → 239 taken 29937 times.
|
29937 | insert.addBindValue(sqlValue(email.types.join(QLatin1Char(',')))); |
| 730 |
2/2✓ Branch 244 → 245 taken 29937 times.
✓ Branch 245 → 246 taken 29937 times.
|
29937 | insert.addBindValue(sqlValue(email.preferred)); |
| 731 |
4/5✓ Branch 248 → 249 taken 29937 times.
✓ Branch 249 → 250 taken 29936 times.
✓ Branch 249 → 252 taken 1 time.
✓ Branch 250 → 251 taken 29936 times.
✗ Branch 250 → 252 not taken.
|
29937 | ok = insert.exec() && ok; |
| 732 | 29937 | } | |
| 733 |
4/4✓ Branch 258 → 259 taken 30498 times.
✓ Branch 259 → 260 taken 30498 times.
✓ Branch 289 → 261 taken 423 times.
✓ Branch 289 → 290 taken 30498 times.
|
30921 | for (const QString &group : card.categories) { |
| 734 |
1/1✓ Branch 262 → 263 taken 423 times.
|
423 | QSqlQuery insert(m_db); |
| 735 |
1/1✓ Branch 266 → 267 taken 423 times.
|
423 | insert.prepare(QStringLiteral("INSERT INTO card_groups (card_id, name) VALUES (?, ?)")); |
| 736 |
2/2✓ Branch 271 → 272 taken 423 times.
✓ Branch 272 → 273 taken 423 times.
|
423 | insert.addBindValue(sqlValue(cardId)); |
| 737 |
2/2✓ Branch 277 → 278 taken 423 times.
✓ Branch 278 → 279 taken 423 times.
|
423 | insert.addBindValue(sqlValue(group)); |
| 738 |
3/5✓ Branch 281 → 282 taken 423 times.
✓ Branch 282 → 283 taken 423 times.
✗ Branch 282 → 285 not taken.
✓ Branch 283 → 284 taken 423 times.
✗ Branch 283 → 285 not taken.
|
423 | ok = insert.exec() && ok; |
| 739 | 423 | } | |
| 740 |
4/4✓ Branch 290 → 291 taken 30498 times.
✓ Branch 291 → 292 taken 30498 times.
✓ Branch 321 → 293 taken 146 times.
✓ Branch 321 → 322 taken 30498 times.
|
30644 | for (const QString &member : card.members) { |
| 741 |
1/1✓ Branch 294 → 295 taken 146 times.
|
146 | QSqlQuery insert(m_db); |
| 742 |
1/1✓ Branch 298 → 299 taken 146 times.
|
146 | insert.prepare(QStringLiteral("INSERT INTO card_members (card_id, member) VALUES (?, ?)")); |
| 743 |
2/2✓ Branch 303 → 304 taken 146 times.
✓ Branch 304 → 305 taken 146 times.
|
146 | insert.addBindValue(sqlValue(cardId)); |
| 744 |
2/2✓ Branch 309 → 310 taken 146 times.
✓ Branch 310 → 311 taken 146 times.
|
146 | insert.addBindValue(sqlValue(member)); |
| 745 |
3/5✓ Branch 313 → 314 taken 146 times.
✓ Branch 314 → 315 taken 146 times.
✗ Branch 314 → 317 not taken.
✓ Branch 315 → 316 taken 146 times.
✗ Branch 315 → 317 not taken.
|
146 | ok = insert.exec() && ok; |
| 746 | 146 | } | |
| 747 |
4/5✓ Branch 322 → 323 taken 30498 times.
✓ Branch 323 → 324 taken 30498 times.
✗ Branch 323 → 326 not taken.
✓ Branch 324 → 325 taken 30497 times.
✓ Branch 324 → 326 taken 1 time.
|
60996 | return ContactPeopleStore::writeRows(m_db, cardId, card) && ok; |
| 748 | 30498 | } | |
| 749 | |||
| 750 | 59 | bool ContactStore::removeCard(qint64 bookId, const QString &uid) { | |
| 751 |
1/1✓ Branch 2 → 3 taken 59 times.
|
59 | QSqlQuery q(m_db); |
| 752 |
1/1✓ Branch 6 → 7 taken 59 times.
|
59 | q.prepare(QStringLiteral("DELETE FROM cards WHERE book_id = ? AND uid = ?")); |
| 753 |
2/2✓ Branch 11 → 12 taken 59 times.
✓ Branch 12 → 13 taken 59 times.
|
59 | q.addBindValue(sqlValue(bookId)); |
| 754 |
2/2✓ Branch 17 → 18 taken 59 times.
✓ Branch 18 → 19 taken 59 times.
|
59 | q.addBindValue(sqlValue(uid)); |
| 755 |
5/6✓ Branch 21 → 22 taken 59 times.
✓ Branch 22 → 23 taken 56 times.
✓ Branch 22 → 26 taken 3 times.
✓ Branch 23 → 24 taken 56 times.
✓ Branch 24 → 25 taken 56 times.
✗ Branch 24 → 26 not taken.
|
118 | return q.exec() && q.numRowsAffected() > 0; |
| 756 | 59 | } | |
| 757 | |||
| 758 | 46 | bool ContactStore::setSyncState(qint64 bookId, const QString &uid, const QString &state) { | |
| 759 |
1/1✓ Branch 2 → 3 taken 46 times.
|
46 | QSqlQuery q(m_db); |
| 760 |
1/1✓ Branch 6 → 7 taken 46 times.
|
46 | q.prepare(QStringLiteral("UPDATE cards SET sync_state = ? WHERE book_id = ? AND uid = ?")); |
| 761 |
2/2✓ Branch 11 → 12 taken 46 times.
✓ Branch 12 → 13 taken 46 times.
|
46 | q.addBindValue(sqlValue(state)); |
| 762 |
2/2✓ Branch 17 → 18 taken 46 times.
✓ Branch 18 → 19 taken 46 times.
|
46 | q.addBindValue(sqlValue(bookId)); |
| 763 |
2/2✓ Branch 23 → 24 taken 46 times.
✓ Branch 24 → 25 taken 46 times.
|
46 | q.addBindValue(sqlValue(uid)); |
| 764 |
1/1✓ Branch 27 → 28 taken 46 times.
|
92 | return q.exec(); |
| 765 | 46 | } | |
| 766 | |||
| 767 | 14159 | qint64 ContactStore::addLocalCard(const ContactCard &input) { | |
| 768 | 14159 | ContactCard card = input; | |
| 769 |
2/2✓ Branch 4 → 5 taken 5 times.
✓ Branch 4 → 10 taken 14154 times.
|
14159 | if (card.uid.isEmpty()) |
| 770 |
2/2✓ Branch 5 → 6 taken 5 times.
✓ Branch 6 → 7 taken 5 times.
|
5 | card.uid = QUuid::createUuid().toString(QUuid::WithoutBraces); |
| 771 | 14159 | StoredCard stored; | |
| 772 |
1/1✓ Branch 10 → 11 taken 14159 times.
|
14159 | stored.bookId = localBookId(); |
| 773 | 14159 | stored.uid = card.uid; | |
| 774 |
1/1✓ Branch 16 → 17 taken 14159 times.
|
28318 | stored.raw = VCardCodec::create(card); |
| 775 |
2/3✗ Branch 23 → 24 not taken.
✓ Branch 23 → 25 taken 14159 times.
✓ Branch 25 → 26 taken 14159 times.
|
28318 | return stored.raw.isEmpty() ? 0 : putCard(stored); |
| 776 | 14159 | } | |
| 777 | |||
| 778 | // ─── outbox ──────────────────────────────────────────────── | ||
| 779 | |||
| 780 | 304 | qint64 ContactStore::enqueueOutbox(qint64 bookId, const QString &uid, const QString &operation, const QString &href, | |
| 781 | const QString &baseEtag, qint64 dependsOn) { | ||
| 782 |
3/3✓ Branch 2 → 3 taken 304 times.
✓ Branch 4 → 5 taken 118 times.
✓ Branch 4 → 44 taken 186 times.
|
304 | if (auto existing = outboxEntry(bookId, uid)) { |
| 783 | // Coalesce: the newest operation wins; the base stays the one the first | ||
| 784 | // queued change was made on (what the server still has). | ||
| 785 | 118 | existing->operation = operation; | |
| 786 | 118 | existing->revision += 1; | |
| 787 |
2/2✓ Branch 10 → 11 taken 94 times.
✓ Branch 10 → 13 taken 24 times.
|
118 | if (existing->href.isEmpty()) |
| 788 | 94 | existing->href = href; | |
| 789 | 118 | existing->attempts = 0; | |
| 790 | 118 | existing->nextAttempt = 0; | |
| 791 | // Sprint 107 F107-INV-1 (as the calendar store, S106-130): a new change | ||
| 792 | // re-opens an entry that ended 'conflict' or 'failed' — it is pushed | ||
| 793 | // again and the conflict is re-evaluated with the newest edit. It used | ||
| 794 | // to stay parked while the card showed "pending" and its banner was gone. | ||
| 795 |
2/2✓ Branch 18 → 19 taken 1 time.
✓ Branch 18 → 34 taken 117 times.
|
118 | if (existing->state != QLatin1String("pending")) { |
| 796 | 1 | existing->state = QStringLiteral("pending"); | |
| 797 | 1 | existing->lastError.clear(); | |
| 798 | 1 | existing->serverRaw.clear(); | |
| 799 | 1 | existing->serverEtag.clear(); | |
| 800 |
1/1✓ Branch 33 → 34 taken 1 time.
|
1 | existing->conflictFields.clear(); |
| 801 | } | ||
| 802 |
1/2✗ Branch 34 → 35 not taken.
✓ Branch 34 → 37 taken 118 times.
|
118 | if (dependsOn > 0) |
| 803 | ✗ | existing->dependsOn = dependsOn; | |
| 804 |
2/3✓ Branch 38 → 39 taken 118 times.
✓ Branch 39 → 40 taken 118 times.
✗ Branch 39 → 42 not taken.
|
118 | return updateOutbox(*existing) ? existing->id : 0; |
| 805 | 304 | } | |
| 806 |
1/1✓ Branch 48 → 50 taken 186 times.
|
186 | QSqlQuery q(m_db); |
| 807 |
1/1✓ Branch 53 → 54 taken 186 times.
|
186 | q.prepare(QStringLiteral("INSERT INTO contact_outbox (book_id, uid, operation, href, base_etag, depends_on) " |
| 808 | "VALUES (?, ?, ?, ?, ?, ?)")); | ||
| 809 |
2/2✓ Branch 58 → 59 taken 186 times.
✓ Branch 59 → 60 taken 186 times.
|
186 | q.addBindValue(sqlValue(bookId)); |
| 810 |
2/2✓ Branch 64 → 65 taken 186 times.
✓ Branch 65 → 66 taken 186 times.
|
186 | q.addBindValue(sqlValue(uid)); |
| 811 |
2/2✓ Branch 70 → 71 taken 186 times.
✓ Branch 71 → 72 taken 186 times.
|
186 | q.addBindValue(sqlValue(operation)); |
| 812 |
2/2✓ Branch 76 → 77 taken 186 times.
✓ Branch 77 → 78 taken 186 times.
|
186 | q.addBindValue(sqlValue(href)); |
| 813 |
2/2✓ Branch 82 → 83 taken 186 times.
✓ Branch 83 → 84 taken 186 times.
|
186 | q.addBindValue(sqlValue(baseEtag)); |
| 814 |
2/2✓ Branch 88 → 89 taken 186 times.
✓ Branch 89 → 90 taken 186 times.
|
186 | q.addBindValue(sqlValue(dependsOn)); |
| 815 |
3/3✓ Branch 92 → 93 taken 186 times.
✓ Branch 93 → 94 taken 1 time.
✓ Branch 93 → 110 taken 185 times.
|
186 | if (!q.exec()) { |
| 816 |
8/8✓ Branch 94 → 95 taken 1 time.
✓ Branch 98 → 99 taken 1 time.
✓ Branch 99 → 100 taken 1 time.
✓ Branch 100 → 101 taken 1 time.
✓ Branch 101 → 102 taken 1 time.
✓ Branch 102 → 103 taken 1 time.
✓ Branch 108 → 96 taken 1 time.
✓ Branch 108 → 109 taken 1 time.
|
2 | qCWarning(lcContactStore) << "enqueueOutbox failed:" << q.lastError().text(); |
| 817 | 1 | return 0; | |
| 818 | } | ||
| 819 |
2/2✓ Branch 110 → 111 taken 185 times.
✓ Branch 111 → 112 taken 185 times.
|
185 | return q.lastInsertId().toLongLong(); |
| 820 | 186 | } | |
| 821 | |||
| 822 | 1941 | QList<ContactOutboxEntry> ContactStore::outboxEntries() const { | |
| 823 | 1941 | QList<ContactOutboxEntry> result; | |
| 824 |
1/1✓ Branch 2 → 3 taken 1941 times.
|
1941 | QSqlQuery q(m_db); |
| 825 |
2/2✓ Branch 8 → 9 taken 1941 times.
✓ Branch 9 → 10 taken 1941 times.
|
3882 | q.exec(QStringLiteral("SELECT %1 FROM contact_outbox ORDER BY id").arg(QLatin1String(kOutboxColumns))); |
| 826 |
3/3✓ Branch 18 → 19 taken 3133 times.
✓ Branch 19 → 14 taken 1192 times.
✓ Branch 19 → 20 taken 1941 times.
|
3133 | while (q.next()) |
| 827 |
2/2✓ Branch 14 → 15 taken 1192 times.
✓ Branch 15 → 16 taken 1192 times.
|
1192 | result.append(outboxFrom(q)); |
| 828 | 1941 | return result; | |
| 829 | 1941 | } | |
| 830 | |||
| 831 | 6942 | std::optional<ContactOutboxEntry> ContactStore::outboxEntry(qint64 bookId, const QString &uid) const { | |
| 832 |
1/1✓ Branch 2 → 3 taken 6942 times.
|
6942 | QSqlQuery q(m_db); |
| 833 |
1/1✓ Branch 9 → 10 taken 6942 times.
|
20826 | q.prepare(QStringLiteral("SELECT %1 FROM contact_outbox WHERE book_id = ? AND uid = ?") |
| 834 |
1/1✓ Branch 8 → 9 taken 6942 times.
|
20826 | .arg(QLatin1String(kOutboxColumns))); |
| 835 |
2/2✓ Branch 15 → 16 taken 6942 times.
✓ Branch 16 → 17 taken 6942 times.
|
6942 | q.addBindValue(sqlValue(bookId)); |
| 836 |
2/2✓ Branch 21 → 22 taken 6942 times.
✓ Branch 22 → 23 taken 6942 times.
|
6942 | q.addBindValue(sqlValue(uid)); |
| 837 |
8/8✓ Branch 25 → 26 taken 6942 times.
✓ Branch 26 → 27 taken 6941 times.
✓ Branch 26 → 30 taken 1 time.
✓ Branch 27 → 28 taken 6941 times.
✓ Branch 28 → 29 taken 748 times.
✓ Branch 28 → 30 taken 6193 times.
✓ Branch 31 → 32 taken 748 times.
✓ Branch 31 → 36 taken 6194 times.
|
6942 | if (q.exec() && q.next()) |
| 838 |
1/1✓ Branch 32 → 33 taken 748 times.
|
748 | return outboxFrom(q); |
| 839 | 6194 | return std::nullopt; | |
| 840 | 6942 | } | |
| 841 | |||
| 842 | ✗ | std::optional<ContactOutboxEntry> ContactStore::outboxEntryById(qint64 id) const { | |
| 843 | ✗ | QSqlQuery q(m_db); | |
| 844 | ✗ | q.prepare(QStringLiteral("SELECT %1 FROM contact_outbox WHERE id = ?").arg(QLatin1String(kOutboxColumns))); | |
| 845 | ✗ | q.addBindValue(sqlValue(id)); | |
| 846 | ✗ | if (q.exec() && q.next()) | |
| 847 | ✗ | return outboxFrom(q); | |
| 848 | ✗ | return std::nullopt; | |
| 849 | ✗ | } | |
| 850 | |||
| 851 | 225 | bool ContactStore::updateOutbox(const ContactOutboxEntry &entry) { | |
| 852 |
1/1✓ Branch 2 → 3 taken 225 times.
|
225 | QSqlQuery q(m_db); |
| 853 |
1/1✓ Branch 6 → 7 taken 225 times.
|
225 | q.prepare(QStringLiteral( |
| 854 | "UPDATE contact_outbox SET operation = ?, href = ?, base_etag = ?, revision = ?, attempts = ?, " | ||
| 855 | "next_attempt = ?, last_error = ?, state = ?, server_raw = ?, server_etag = ?, conflict_fields = ?, " | ||
| 856 | "depends_on = ?, base_raw = ? WHERE id = ?")); | ||
| 857 |
2/2✓ Branch 11 → 12 taken 225 times.
✓ Branch 12 → 13 taken 225 times.
|
225 | q.addBindValue(sqlValue(entry.operation)); |
| 858 |
2/2✓ Branch 17 → 18 taken 225 times.
✓ Branch 18 → 19 taken 225 times.
|
225 | q.addBindValue(sqlValue(entry.href)); |
| 859 |
2/2✓ Branch 23 → 24 taken 225 times.
✓ Branch 24 → 25 taken 225 times.
|
225 | q.addBindValue(sqlValue(entry.baseEtag)); |
| 860 |
2/2✓ Branch 29 → 30 taken 225 times.
✓ Branch 30 → 31 taken 225 times.
|
225 | q.addBindValue(sqlValue(entry.revision)); |
| 861 |
2/2✓ Branch 35 → 36 taken 225 times.
✓ Branch 36 → 37 taken 225 times.
|
225 | q.addBindValue(sqlValue(entry.attempts)); |
| 862 |
2/2✓ Branch 41 → 42 taken 225 times.
✓ Branch 42 → 43 taken 225 times.
|
225 | q.addBindValue(sqlValue(entry.nextAttempt)); |
| 863 |
2/2✓ Branch 47 → 48 taken 225 times.
✓ Branch 48 → 49 taken 225 times.
|
225 | q.addBindValue(sqlValue(entry.lastError)); |
| 864 |
2/2✓ Branch 53 → 54 taken 225 times.
✓ Branch 54 → 55 taken 225 times.
|
225 | q.addBindValue(sqlValue(entry.state)); |
| 865 |
2/2✓ Branch 59 → 60 taken 225 times.
✓ Branch 60 → 61 taken 225 times.
|
225 | q.addBindValue(sqlValue(entry.serverRaw)); |
| 866 |
2/2✓ Branch 65 → 66 taken 225 times.
✓ Branch 66 → 67 taken 225 times.
|
225 | q.addBindValue(sqlValue(entry.serverEtag)); |
| 867 |
3/3✓ Branch 72 → 73 taken 225 times.
✓ Branch 74 → 75 taken 225 times.
✓ Branch 75 → 76 taken 225 times.
|
225 | q.addBindValue(sqlValue(entry.conflictFields.join(QLatin1Char(',')))); |
| 868 |
2/2✓ Branch 81 → 82 taken 225 times.
✓ Branch 82 → 83 taken 225 times.
|
225 | q.addBindValue(sqlValue(entry.dependsOn)); |
| 869 |
2/2✓ Branch 87 → 88 taken 225 times.
✓ Branch 88 → 89 taken 225 times.
|
225 | q.addBindValue(sqlValue(entry.baseRaw)); |
| 870 |
2/2✓ Branch 93 → 94 taken 225 times.
✓ Branch 94 → 95 taken 225 times.
|
225 | q.addBindValue(sqlValue(entry.id)); |
| 871 |
1/1✓ Branch 97 → 98 taken 225 times.
|
450 | return q.exec(); |
| 872 | 225 | } | |
| 873 | |||
| 874 | 83 | bool ContactStore::removeOutbox(qint64 bookId, const QString &uid, int revision) { | |
| 875 |
1/1✓ Branch 2 → 3 taken 83 times.
|
83 | QSqlQuery q(m_db); |
| 876 |
2/2✓ Branch 3 → 4 taken 47 times.
✓ Branch 3 → 29 taken 36 times.
|
83 | if (revision >= 0) { |
| 877 |
1/1✓ Branch 7 → 8 taken 47 times.
|
47 | q.prepare(QStringLiteral("DELETE FROM contact_outbox WHERE book_id = ? AND uid = ? AND revision = ?")); |
| 878 |
2/2✓ Branch 12 → 13 taken 47 times.
✓ Branch 13 → 14 taken 47 times.
|
47 | q.addBindValue(sqlValue(bookId)); |
| 879 |
2/2✓ Branch 18 → 19 taken 47 times.
✓ Branch 19 → 20 taken 47 times.
|
47 | q.addBindValue(sqlValue(uid)); |
| 880 |
2/2✓ Branch 24 → 25 taken 47 times.
✓ Branch 25 → 26 taken 47 times.
|
47 | q.addBindValue(sqlValue(revision)); |
| 881 | } else { | ||
| 882 |
1/1✓ Branch 32 → 33 taken 36 times.
|
36 | q.prepare(QStringLiteral("DELETE FROM contact_outbox WHERE book_id = ? AND uid = ?")); |
| 883 |
2/2✓ Branch 37 → 38 taken 36 times.
✓ Branch 38 → 39 taken 36 times.
|
36 | q.addBindValue(sqlValue(bookId)); |
| 884 |
2/2✓ Branch 43 → 44 taken 36 times.
✓ Branch 44 → 45 taken 36 times.
|
36 | q.addBindValue(sqlValue(uid)); |
| 885 | } | ||
| 886 |
6/6✓ Branch 48 → 49 taken 83 times.
✓ Branch 49 → 50 taken 82 times.
✓ Branch 49 → 53 taken 1 time.
✓ Branch 50 → 51 taken 82 times.
✓ Branch 51 → 52 taken 66 times.
✓ Branch 51 → 53 taken 16 times.
|
166 | return q.exec() && q.numRowsAffected() > 0; |
| 887 | 83 | } | |
| 888 | |||
| 889 | // ─── meta and legacy ─────────────────────────────────────── | ||
| 890 | |||
| 891 | 345 | QString ContactStore::meta(const QString &key) const { | |
| 892 |
1/1✓ Branch 2 → 3 taken 345 times.
|
345 | QSqlQuery q(m_db); |
| 893 |
1/1✓ Branch 6 → 7 taken 345 times.
|
345 | q.prepare(QStringLiteral("SELECT value FROM contact_meta WHERE key = ?")); |
| 894 |
2/2✓ Branch 11 → 12 taken 345 times.
✓ Branch 12 → 13 taken 345 times.
|
345 | q.addBindValue(sqlValue(key)); |
| 895 |
8/8✓ Branch 15 → 16 taken 345 times.
✓ Branch 16 → 17 taken 344 times.
✓ Branch 16 → 20 taken 1 time.
✓ Branch 17 → 18 taken 344 times.
✓ Branch 18 → 19 taken 16 times.
✓ Branch 18 → 20 taken 328 times.
✓ Branch 21 → 22 taken 16 times.
✓ Branch 21 → 27 taken 329 times.
|
345 | if (q.exec() && q.next()) |
| 896 |
2/2✓ Branch 22 → 23 taken 16 times.
✓ Branch 23 → 24 taken 16 times.
|
16 | return q.value(0).toString(); |
| 897 | 329 | return {}; | |
| 898 | 345 | } | |
| 899 | |||
| 900 | 25 | bool ContactStore::setMeta(const QString &key, const QString &value) { | |
| 901 |
1/1✓ Branch 2 → 3 taken 25 times.
|
25 | QSqlQuery q(m_db); |
| 902 |
1/1✓ Branch 6 → 7 taken 25 times.
|
25 | q.prepare(QStringLiteral("INSERT OR REPLACE INTO contact_meta (key, value) VALUES (?, ?)")); |
| 903 |
2/2✓ Branch 11 → 12 taken 25 times.
✓ Branch 12 → 13 taken 25 times.
|
25 | q.addBindValue(sqlValue(key)); |
| 904 |
2/2✓ Branch 17 → 18 taken 25 times.
✓ Branch 18 → 19 taken 25 times.
|
25 | q.addBindValue(sqlValue(value)); |
| 905 |
1/1✓ Branch 21 → 22 taken 25 times.
|
50 | return q.exec(); |
| 906 | 25 | } | |
| 907 | |||
| 908 | 75 | QList<ContactStore::PendingPhoto> ContactStore::pendingPhotos(qint64 bookId, int limit) const { | |
| 909 |
1/1✓ Branch 2 → 3 taken 75 times.
|
75 | QSqlQuery q(m_db); |
| 910 |
1/1✓ Branch 6 → 7 taken 75 times.
|
75 | q.prepare(QStringLiteral("SELECT id, uid, href, photo_uri FROM cards WHERE book_id = ? AND photo_uri <> '' " |
| 911 | "AND photo_fetched_uri <> photo_uri ORDER BY id LIMIT ?")); | ||
| 912 |
2/2✓ Branch 11 → 12 taken 75 times.
✓ Branch 12 → 13 taken 75 times.
|
75 | q.addBindValue(sqlValue(bookId)); |
| 913 |
2/2✓ Branch 17 → 18 taken 75 times.
✓ Branch 18 → 19 taken 75 times.
|
75 | q.addBindValue(sqlValue(limit)); |
| 914 | 75 | QList<PendingPhoto> pending; | |
| 915 |
2/3✓ Branch 21 → 22 taken 75 times.
✓ Branch 22 → 23 taken 75 times.
✗ Branch 22 → 41 not taken.
|
75 | if (q.exec()) |
| 916 |
3/3✓ Branch 39 → 40 taken 80 times.
✓ Branch 40 → 24 taken 5 times.
✓ Branch 40 → 41 taken 75 times.
|
80 | while (q.next()) |
| 917 |
2/2✓ Branch 24 → 25 taken 5 times.
✓ Branch 25 → 26 taken 5 times.
|
10 | pending.append({q.value(0).toLongLong(), q.value(1).toString(), q.value(2).toString(), q.value(3).toString()}); |
| 918 | 75 | return pending; | |
| 919 | 80 | } | |
| 920 | |||
| 921 | 6 | bool ContactStore::setFetchedPhoto(qint64 cardId, const QString &uri, const QByteArray &bytes) { | |
| 922 |
1/1✓ Branch 2 → 3 taken 6 times.
|
6 | QSqlQuery q(m_db); |
| 923 |
1/1✓ Branch 6 → 7 taken 6 times.
|
6 | q.prepare(QStringLiteral("UPDATE cards SET photo_fetched_uri = ?, photo_thumb = ?, updated_at = datetime('now') " |
| 924 | "WHERE id = ? AND photo_uri = ?")); | ||
| 925 |
2/2✓ Branch 11 → 12 taken 6 times.
✓ Branch 12 → 13 taken 6 times.
|
6 | q.addBindValue(sqlValue(uri)); |
| 926 |
5/5✓ Branch 17 → 18 taken 2 times.
✓ Branch 17 → 20 taken 4 times.
✓ Branch 18 → 19 taken 2 times.
✓ Branch 19 → 21 taken 2 times.
✓ Branch 21 → 22 taken 6 times.
|
6 | q.addBindValue(bytes.isEmpty() ? QVariant(QMetaType(QMetaType::QByteArray)) : QVariant(bytes)); |
| 927 |
2/2✓ Branch 25 → 26 taken 6 times.
✓ Branch 26 → 27 taken 6 times.
|
6 | q.addBindValue(sqlValue(cardId)); |
| 928 |
2/2✓ Branch 31 → 32 taken 6 times.
✓ Branch 32 → 33 taken 6 times.
|
6 | q.addBindValue(sqlValue(uri)); |
| 929 |
5/6✓ Branch 35 → 36 taken 6 times.
✓ Branch 36 → 37 taken 6 times.
✗ Branch 36 → 40 not taken.
✓ Branch 37 → 38 taken 6 times.
✓ Branch 38 → 39 taken 5 times.
✓ Branch 38 → 40 taken 1 time.
|
12 | return q.exec() && q.numRowsAffected() == 1; |
| 930 | 6 | } | |
| 931 | |||
| 932 | 7 | QByteArray ContactStore::fetchedPhoto(qint64 bookId, const QString &uid) const { | |
| 933 |
1/1✓ Branch 2 → 3 taken 7 times.
|
7 | QSqlQuery q(m_db); |
| 934 |
1/1✓ Branch 6 → 7 taken 7 times.
|
7 | q.prepare(QStringLiteral("SELECT photo_thumb FROM cards WHERE book_id = ? AND uid = ? AND photo_uri <> '' " |
| 935 | "AND photo_fetched_uri = photo_uri")); | ||
| 936 |
2/2✓ Branch 11 → 12 taken 7 times.
✓ Branch 12 → 13 taken 7 times.
|
7 | q.addBindValue(sqlValue(bookId)); |
| 937 |
2/2✓ Branch 17 → 18 taken 7 times.
✓ Branch 18 → 19 taken 7 times.
|
7 | q.addBindValue(sqlValue(uid)); |
| 938 |
9/12✓ Branch 21 → 22 taken 7 times.
✓ Branch 22 → 23 taken 7 times.
✗ Branch 22 → 28 not taken.
✓ Branch 23 → 24 taken 7 times.
✓ Branch 24 → 25 taken 6 times.
✓ Branch 24 → 28 taken 1 time.
✓ Branch 25 → 26 taken 6 times.
✓ Branch 26 → 27 taken 6 times.
✓ Branch 30 → 31 taken 6 times.
✓ Branch 30 → 32 taken 1 time.
✗ Branch 55 → 56 not taken.
✗ Branch 55 → 57 not taken.
|
21 | return q.exec() && q.next() ? q.value(0).toByteArray() : QByteArray(); |
| 939 | 7 | } | |
| 940 | |||
| 941 | // ─── Sprint 107 D-21: conflict history ───────────────────── | ||
| 942 | |||
| 943 | 16 | bool ContactStore::addConflictHistory(const ConflictHistory::Entry &entry, qint64 now) { | |
| 944 | 16 | return ConflictHistory::add(m_db, entry, now); | |
| 945 | } | ||
| 946 | |||
| 947 | 890 | QList<ConflictHistory::Entry> ContactStore::conflictHistory(int limit) const { | |
| 948 | 890 | return ConflictHistory::list(m_db, limit); | |
| 949 | } | ||
| 950 | |||
| 951 | 3 | std::optional<ConflictHistory::Entry> ContactStore::conflictHistoryEntry(qint64 id) const { | |
| 952 | 3 | return ConflictHistory::find(m_db, id); | |
| 953 | } | ||
| 954 | |||
| 955 | 2 | bool ContactStore::removeConflictHistory(qint64 id) { return ConflictHistory::remove(m_db, id); } | |
| 956 | |||
| 957 | 4 | QList<qint64> ContactStore::booksWithCard(const QString &uid) const { | |
| 958 | 4 | QList<qint64> books; | |
| 959 |
1/1✓ Branch 2 → 3 taken 4 times.
|
4 | QSqlQuery q(m_db); |
| 960 |
1/1✓ Branch 6 → 7 taken 4 times.
|
4 | q.prepare(QStringLiteral("SELECT book_id FROM cards WHERE uid = ? ORDER BY book_id")); |
| 961 |
2/2✓ Branch 11 → 12 taken 4 times.
✓ Branch 12 → 13 taken 4 times.
|
4 | q.addBindValue(sqlValue(uid)); |
| 962 |
2/3✓ Branch 15 → 16 taken 4 times.
✓ Branch 16 → 17 taken 4 times.
✗ Branch 16 → 25 not taken.
|
4 | if (q.exec()) |
| 963 |
3/3✓ Branch 23 → 24 taken 7 times.
✓ Branch 24 → 18 taken 3 times.
✓ Branch 24 → 25 taken 4 times.
|
7 | while (q.next()) |
| 964 |
3/3✓ Branch 18 → 19 taken 3 times.
✓ Branch 19 → 20 taken 3 times.
✓ Branch 20 → 21 taken 3 times.
|
3 | books.append(q.value(0).toLongLong()); |
| 965 | 4 | return books; | |
| 966 | 4 | } | |
| 967 |