data/ContactQueries.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "ContactQueries.h" | ||
| 2 | |||
| 3 | #include "ContactPeopleFilter.h" | ||
| 4 | |||
| 5 | #include "ContactIndex.h" | ||
| 6 | #include "ContactSqlValue.h" | ||
| 7 | |||
| 8 | #include <QDateTime> | ||
| 9 | #include <QSet> | ||
| 10 | #include <QSqlQuery> | ||
| 11 | #include <algorithm> | ||
| 12 | |||
| 13 | namespace ContactQueries { | ||
| 14 | |||
| 15 | namespace { | ||
| 16 | |||
| 17 | const char *kRowColumns = | ||
| 18 | "c.id, c.book_id, c.uid, c.display_name, c.sort_display, c.sort_family, c.organization, c.primary_address, " | ||
| 19 | "c.address_count, b.display_name, b.account_id, b.read_only, (b.account_id = '' AND b.path = 'local'), " | ||
| 20 | "c.kind, c.sync_state, c.writable, c.has_photo, " | ||
| 21 | "CASE WHEN c.has_photo THEN c.updated_at || ' ' || c.photo_fetched_uri ELSE '' END, " | ||
| 22 | "c.phone, c.job_title, c.locality, c.birthday, " | ||
| 23 | "(SELECT GROUP_CONCAT(g.name, ', ') FROM card_groups g WHERE g.card_id = c.id)"; | ||
| 24 | |||
| 25 | // F100-11: a card in no CATEGORIES group and no member of a group card. | ||
| 26 | const char *kUngrouped = | ||
| 27 | "NOT EXISTS (SELECT 1 FROM card_groups g WHERE g.card_id = c.id) AND " | ||
| 28 | "NOT EXISTS (SELECT 1 FROM cards gc JOIN card_members m ON m.card_id = gc.id WHERE gc.book_id = c.book_id" | ||
| 29 | " AND gc.kind = 'group' AND (m.member = 'urn:uuid:' || c.uid OR m.member = c.uid))"; | ||
| 30 | |||
| 31 | 262 | QString likePattern(const QString &word) { | |
| 32 | 262 | QString escaped = word; | |
| 33 |
1/1✓ Branch 8 → 9 taken 262 times.
|
262 | escaped.replace(QLatin1Char('\\'), QStringLiteral("\\\\")); |
| 34 |
1/1✓ Branch 16 → 17 taken 262 times.
|
262 | escaped.replace(QLatin1Char('%'), QStringLiteral("\\%")); |
| 35 |
1/1✓ Branch 24 → 25 taken 262 times.
|
262 | escaped.replace(QLatin1Char('_'), QStringLiteral("\\_")); |
| 36 |
2/2✓ Branch 31 → 32 taken 262 times.
✓ Branch 32 → 33 taken 262 times.
|
786 | return QLatin1Char('%') + escaped + QLatin1Char('%'); |
| 37 | 262 | } | |
| 38 | |||
| 39 | 2977 | QStringList filterWords(const QString &filter) { | |
| 40 |
2/2✓ Branch 2 → 3 taken 2977 times.
✓ Branch 6 → 7 taken 2977 times.
|
2977 | return ContactIndex::foldForSort(filter).split(QLatin1Char(' '), Qt::SkipEmptyParts); |
| 41 | } | ||
| 42 | |||
| 43 | 5512 | Row rowFrom(const QSqlQuery &q, bool byFamilyName) { | |
| 44 | 5512 | Row row; | |
| 45 |
2/2✓ Branch 2 → 3 taken 5512 times.
✓ Branch 3 → 4 taken 5512 times.
|
5512 | row.cardId = q.value(0).toLongLong(); |
| 46 |
2/2✓ Branch 5 → 6 taken 5512 times.
✓ Branch 6 → 7 taken 5512 times.
|
5512 | row.bookId = q.value(1).toLongLong(); |
| 47 |
2/2✓ Branch 8 → 9 taken 5512 times.
✓ Branch 9 → 10 taken 5512 times.
|
5512 | row.uid = q.value(2).toString(); |
| 48 |
1/1✓ Branch 13 → 14 taken 5512 times.
|
5512 | row.key = cardKey(row.bookId, row.uid); |
| 49 |
2/2✓ Branch 16 → 17 taken 5512 times.
✓ Branch 17 → 18 taken 5512 times.
|
5512 | row.displayName = q.value(3).toString(); |
| 50 |
10/14✓ Branch 21 → 22 taken 16 times.
✓ Branch 21 → 24 taken 5496 times.
✓ Branch 22 → 23 taken 16 times.
✓ Branch 23 → 26 taken 16 times.
✓ Branch 24 → 25 taken 5496 times.
✓ Branch 25 → 26 taken 5496 times.
✓ Branch 28 → 29 taken 5496 times.
✓ Branch 28 → 30 taken 16 times.
✓ Branch 30 → 31 taken 16 times.
✓ Branch 30 → 32 taken 5496 times.
✗ Branch 135 → 136 not taken.
✗ Branch 135 → 137 not taken.
✗ Branch 139 → 140 not taken.
✗ Branch 139 → 141 not taken.
|
5512 | row.sortKey = byFamilyName ? q.value(5).toString() : q.value(4).toString(); |
| 51 |
1/1✓ Branch 32 → 33 taken 5512 times.
|
5512 | row.section = ContactIndex::sectionOf(row.sortKey); |
| 52 |
2/2✓ Branch 35 → 36 taken 5512 times.
✓ Branch 36 → 37 taken 5512 times.
|
5512 | row.organization = q.value(6).toString(); |
| 53 |
2/2✓ Branch 40 → 41 taken 5512 times.
✓ Branch 41 → 42 taken 5512 times.
|
5512 | row.primaryAddress = q.value(7).toString(); |
| 54 |
2/2✓ Branch 45 → 46 taken 5512 times.
✓ Branch 46 → 47 taken 5512 times.
|
5512 | row.addressCount = q.value(8).toInt(); |
| 55 |
2/2✓ Branch 48 → 49 taken 5512 times.
✓ Branch 49 → 50 taken 5512 times.
|
5512 | row.bookName = q.value(9).toString(); |
| 56 |
2/2✓ Branch 53 → 54 taken 5512 times.
✓ Branch 54 → 55 taken 5512 times.
|
5512 | row.accountId = q.value(10).toString(); |
| 57 |
2/2✓ Branch 58 → 59 taken 5512 times.
✓ Branch 59 → 60 taken 5512 times.
|
5512 | row.bookReadOnly = q.value(11).toBool(); |
| 58 |
2/2✓ Branch 61 → 62 taken 5512 times.
✓ Branch 62 → 63 taken 5512 times.
|
5512 | row.localBook = q.value(12).toBool(); |
| 59 |
2/2✓ Branch 64 → 65 taken 5512 times.
✓ Branch 65 → 66 taken 5512 times.
|
5512 | row.kind = q.value(13).toString(); |
| 60 |
2/2✓ Branch 69 → 70 taken 5512 times.
✓ Branch 70 → 71 taken 5512 times.
|
5512 | row.syncState = q.value(14).toString(); |
| 61 |
7/12✓ Branch 74 → 75 taken 5512 times.
✓ Branch 75 → 76 taken 5512 times.
✓ Branch 76 → 77 taken 5512 times.
✗ Branch 76 → 82 not taken.
✓ Branch 77 → 78 taken 5433 times.
✓ Branch 77 → 82 taken 79 times.
✓ Branch 80 → 81 taken 5433 times.
✗ Branch 80 → 82 not taken.
✓ Branch 83 → 84 taken 5512 times.
✗ Branch 83 → 85 not taken.
✗ Branch 179 → 180 not taken.
✗ Branch 179 → 181 not taken.
|
5512 | row.writable = q.value(15).toBool() && !row.bookReadOnly && row.syncState != QLatin1String("legacy"); |
| 62 |
2/2✓ Branch 85 → 86 taken 5512 times.
✓ Branch 86 → 87 taken 5512 times.
|
5512 | row.hasPhoto = q.value(16).toBool(); |
| 63 |
2/2✓ Branch 88 → 89 taken 5512 times.
✓ Branch 89 → 90 taken 5512 times.
|
5512 | row.photoTag = q.value(17).toString(); |
| 64 |
2/2✓ Branch 93 → 94 taken 5512 times.
✓ Branch 94 → 95 taken 5512 times.
|
5512 | row.phone = q.value(18).toString(); |
| 65 |
2/2✓ Branch 98 → 99 taken 5512 times.
✓ Branch 99 → 100 taken 5512 times.
|
5512 | row.title = q.value(19).toString(); |
| 66 |
2/2✓ Branch 103 → 104 taken 5512 times.
✓ Branch 104 → 105 taken 5512 times.
|
5512 | row.locality = q.value(20).toString(); |
| 67 |
2/2✓ Branch 108 → 109 taken 5512 times.
✓ Branch 109 → 110 taken 5512 times.
|
5512 | row.birthday = q.value(21).toString(); |
| 68 |
2/2✓ Branch 113 → 114 taken 5512 times.
✓ Branch 114 → 115 taken 5512 times.
|
5512 | row.groups = q.value(22).toString(); |
| 69 | 5512 | return row; | |
| 70 | ✗ | } | |
| 71 | |||
| 72 | // WHERE clause of a card view (without the filter); binds via `binds`. | ||
| 73 | 2039 | QString viewCondition(const View &view, QVariantMap *binds) { | |
| 74 |
5/5✓ Branch 2 → 3 taken 4 times.
✓ Branch 2 → 16 taken 5 times.
✓ Branch 2 → 37 taken 1 time.
✓ Branch 2 → 54 taken 1 time.
✓ Branch 2 → 59 taken 2028 times.
|
2039 | switch (view.kind) { |
| 75 | 4 | case ViewKind::Book: | |
| 76 |
1/1✓ Branch 7 → 8 taken 4 times.
|
8 | binds->insert(QStringLiteral(":book"), view.bookId); |
| 77 | 4 | return QStringLiteral("c.book_id = :book AND c.kind <> 'group'"); | |
| 78 | 5 | case ViewKind::Group: | |
| 79 |
1/1✓ Branch 20 → 21 taken 5 times.
|
10 | binds->insert(QStringLiteral(":book"), view.bookId); |
| 80 |
1/1✓ Branch 28 → 29 taken 5 times.
|
10 | binds->insert(QStringLiteral(":group"), view.group); |
| 81 | 5 | return QStringLiteral( | |
| 82 | "c.book_id = :book AND c.kind <> 'group' AND (" | ||
| 83 | " EXISTS (SELECT 1 FROM card_groups g WHERE g.card_id = c.id AND g.name = :group) OR" | ||
| 84 | " EXISTS (SELECT 1 FROM cards gc JOIN card_members m ON m.card_id = gc.id WHERE gc.book_id = c.book_id" | ||
| 85 | " AND gc.kind = 'group' AND gc.display_name = :group" | ||
| 86 | " AND (m.member = 'urn:uuid:' || c.uid OR m.member = c.uid)))"); | ||
| 87 | 1 | case ViewKind::Ungrouped: | |
| 88 |
1/1✓ Branch 41 → 42 taken 1 time.
|
2 | binds->insert(QStringLiteral(":book"), view.bookId); |
| 89 |
1/1✓ Branch 50 → 51 taken 1 time.
|
2 | return QStringLiteral("c.book_id = :book AND c.kind <> 'group' AND %1").arg(QLatin1String(kUngrouped)); |
| 90 | 1 | case ViewKind::Duplicates: | |
| 91 | 1 | return QStringLiteral( | |
| 92 | "b.show_in_all = 1 AND c.kind <> 'group' AND EXISTS (SELECT 1 FROM cards o WHERE o.book_id = c.book_id" | ||
| 93 | " AND o.id <> c.id AND o.kind <> 'group' AND ((c.sort_display <> '' AND o.sort_display = c.sort_display) OR" | ||
| 94 | " EXISTS (SELECT 1 FROM card_addresses a JOIN card_addresses oa ON oa.address_key = a.address_key" | ||
| 95 | " WHERE a.card_id = c.id AND oa.card_id = o.id)))"); | ||
| 96 | 2028 | default: | |
| 97 | 2028 | return QStringLiteral("b.show_in_all = 1 AND c.kind <> 'group'"); | |
| 98 | } | ||
| 99 | } | ||
| 100 | |||
| 101 | 2039 | QString filterCondition(const QString &filter, QVariantMap *binds) { | |
| 102 | // Sprint 103 S103.6.7: related:, via:, met:, near:, interest:, has: first. | ||
| 103 |
1/1✓ Branch 2 → 3 taken 2039 times.
|
2039 | const ContactPeopleFilter::Split people = ContactPeopleFilter::split(filter, binds); |
| 104 | 2039 | QStringList parts = people.conditions; | |
| 105 |
1/1✓ Branch 4 → 5 taken 2039 times.
|
2039 | const QStringList words = filterWords(people.rest); |
| 106 |
2/2✓ Branch 31 → 6 taken 150 times.
✓ Branch 31 → 32 taken 2039 times.
|
2189 | for (int i = 0; i < words.size(); ++i) { |
| 107 |
1/1✓ Branch 10 → 11 taken 150 times.
|
300 | const QString name = QStringLiteral(":w%1").arg(i); |
| 108 |
2/2✓ Branch 17 → 18 taken 150 times.
✓ Branch 18 → 19 taken 150 times.
|
300 | parts << QStringLiteral("c.search_text LIKE %1 ESCAPE '\\'").arg(name); |
| 109 |
2/2✓ Branch 23 → 24 taken 150 times.
✓ Branch 25 → 26 taken 150 times.
|
150 | binds->insert(name, likePattern(words.at(i))); |
| 110 | 150 | } | |
| 111 |
1/1✓ Branch 35 → 36 taken 2039 times.
|
4078 | return parts.join(QStringLiteral(" AND ")); |
| 112 | 2039 | } | |
| 113 | |||
| 114 | 2039 | void bindAll(QSqlQuery *q, const QVariantMap &binds) { | |
| 115 |
4/4✓ Branch 2 → 3 taken 2039 times.
✓ Branch 11 → 12 taken 2252 times.
✓ Branch 13 → 4 taken 213 times.
✓ Branch 13 → 14 taken 2039 times.
|
2252 | for (auto it = binds.cbegin(); it != binds.cend(); ++it) |
| 116 |
2/2✓ Branch 6 → 7 taken 213 times.
✓ Branch 8 → 9 taken 213 times.
|
213 | q->bindValue(it.key(), sqlValue(it.value())); |
| 117 | 2039 | } | |
| 118 | |||
| 119 | // Collected addresses (not in any card) as list rows, sorted. | ||
| 120 | 63 | QList<Row> collectedRows(QSqlDatabase db, const QString &filter, const QSet<QString> &onlyKeys = {}) { | |
| 121 | 63 | QList<Row> rows; | |
| 122 |
1/1✓ Branch 2 → 3 taken 63 times.
|
63 | QSqlQuery q(db); |
| 123 |
1/1✓ Branch 6 → 7 taken 63 times.
|
63 | q.exec(QStringLiteral( |
| 124 | "SELECT ca.address_key, ca.address, ca.display_name, COALESCE(u.use_count, 0) FROM collected_addresses ca " | ||
| 125 | "LEFT JOIN address_usage u ON u.address_key = ca.address_key " | ||
| 126 | "WHERE ca.address_key NOT IN (SELECT address_key FROM card_addresses)")); | ||
| 127 |
1/1✓ Branch 9 → 10 taken 63 times.
|
63 | const QStringList words = filterWords(filter); |
| 128 |
3/3✓ Branch 79 → 80 taken 497 times.
✓ Branch 80 → 11 taken 434 times.
✓ Branch 80 → 81 taken 63 times.
|
497 | while (q.next()) { |
| 129 |
10/16✓ Branch 12 → 13 taken 117 times.
✓ Branch 12 → 18 taken 317 times.
✓ Branch 13 → 14 taken 117 times.
✓ Branch 14 → 15 taken 117 times.
✗ Branch 16 → 17 not taken.
✓ Branch 16 → 18 taken 117 times.
✓ Branch 19 → 20 taken 117 times.
✓ Branch 19 → 21 taken 317 times.
✓ Branch 21 → 22 taken 117 times.
✓ Branch 21 → 23 taken 317 times.
✗ Branch 23 → 24 not taken.
✓ Branch 23 → 25 taken 434 times.
✗ Branch 94 → 95 not taken.
✗ Branch 94 → 96 not taken.
✗ Branch 98 → 99 not taken.
✗ Branch 98 → 100 not taken.
|
434 | if (!onlyKeys.isEmpty() && !onlyKeys.contains(q.value(0).toString())) |
| 130 | ✗ | continue; | |
| 131 | 434 | Row row; | |
| 132 | 434 | row.collected = true; | |
| 133 |
3/3✓ Branch 25 → 26 taken 434 times.
✓ Branch 26 → 27 taken 434 times.
✓ Branch 30 → 31 taken 434 times.
|
868 | row.key = QStringLiteral("collected:") + q.value(0).toString(); |
| 134 |
2/2✓ Branch 37 → 38 taken 434 times.
✓ Branch 38 → 39 taken 434 times.
|
434 | row.primaryAddress = q.value(1).toString(); |
| 135 |
8/10✓ Branch 42 → 43 taken 434 times.
✓ Branch 43 → 44 taken 434 times.
✓ Branch 45 → 46 taken 31 times.
✓ Branch 45 → 47 taken 403 times.
✓ Branch 47 → 48 taken 403 times.
✓ Branch 48 → 49 taken 403 times.
✓ Branch 51 → 52 taken 403 times.
✓ Branch 51 → 53 taken 31 times.
✗ Branch 119 → 120 not taken.
✗ Branch 119 → 121 not taken.
|
434 | row.displayName = q.value(2).toString().isEmpty() ? row.primaryAddress : q.value(2).toString(); |
| 136 |
1/1✓ Branch 55 → 56 taken 434 times.
|
434 | row.sortKey = ContactIndex::foldForSort(row.displayName); |
| 137 |
1/1✓ Branch 58 → 59 taken 434 times.
|
434 | row.section = ContactIndex::sectionOf(row.sortKey); |
| 138 | 434 | row.addressCount = 1; | |
| 139 | 434 | row.writable = false; | |
| 140 |
2/2✓ Branch 61 → 62 taken 434 times.
✓ Branch 62 → 63 taken 434 times.
|
434 | row.useCount = q.value(3).toInt(); |
| 141 |
3/3✓ Branch 66 → 67 taken 434 times.
✓ Branch 67 → 68 taken 434 times.
✓ Branch 68 → 69 taken 434 times.
|
434 | const QString haystack = ContactIndex::foldForSort(row.displayName + QLatin1Char(' ') + row.primaryAddress); |
| 142 |
2/3✓ Branch 73 → 74 taken 434 times.
✓ Branch 74 → 75 taken 434 times.
✗ Branch 74 → 76 not taken.
|
434 | if (std::all_of(words.cbegin(), words.cend(), [&](const QString &w) { return haystack.contains(w); })) |
| 143 |
1/1✓ Branch 75 → 76 taken 434 times.
|
434 | rows.append(row); |
| 144 | 434 | } | |
| 145 |
3/3✓ Branch 81 → 82 taken 63 times.
✓ Branch 82 → 83 taken 63 times.
✓ Branch 83 → 84 taken 63 times.
|
63 | std::sort(rows.begin(), rows.end(), [](const Row &a, const Row &b) { |
| 146 |
2/2✓ Branch 3 → 4 taken 2172 times.
✓ Branch 3 → 7 taken 93 times.
|
2265 | return a.sortKey == b.sortKey ? a.key < b.key : sortKeyLess(a.sortKey, b.sortKey); |
| 147 | }); | ||
| 148 | 63 | return rows; | |
| 149 | 63 | } | |
| 150 | |||
| 151 | // D-9: the 25 addresses used most within 180 days, as card or collected rows. | ||
| 152 | 875 | QList<Row> frequentRows(QSqlDatabase db, const QString &filter) { | |
| 153 |
1/1✓ Branch 2 → 3 taken 875 times.
|
875 | QSqlQuery top(db); |
| 154 |
1/1✓ Branch 6 → 7 taken 875 times.
|
875 | top.prepare(QStringLiteral("SELECT address_key, use_count FROM address_usage WHERE use_count > 0 AND last_used >= ? " |
| 155 | "ORDER BY use_count DESC, last_used DESC LIMIT 25")); | ||
| 156 |
5/5✓ Branch 10 → 11 taken 875 times.
✓ Branch 11 → 12 taken 875 times.
✓ Branch 12 → 13 taken 875 times.
✓ Branch 14 → 15 taken 875 times.
✓ Branch 15 → 16 taken 875 times.
|
875 | top.addBindValue(sqlValue(QDateTime::currentDateTimeUtc().addDays(-180).toString(Qt::ISODate))); |
| 157 | 875 | QList<Row> rows; | |
| 158 | 875 | QSet<QString> seenCards; | |
| 159 | 875 | QSet<QString> collectedKeys; | |
| 160 |
2/3✓ Branch 23 → 24 taken 875 times.
✗ Branch 24 → 25 not taken.
✓ Branch 24 → 27 taken 875 times.
|
875 | if (!top.exec()) |
| 161 | ✗ | return rows; | |
| 162 | 875 | QHash<QString, int> usage; | |
| 163 |
3/3✓ Branch 72 → 73 taken 1089 times.
✓ Branch 73 → 28 taken 214 times.
✓ Branch 73 → 74 taken 875 times.
|
1089 | while (top.next()) { |
| 164 |
2/2✓ Branch 28 → 29 taken 214 times.
✓ Branch 29 → 30 taken 214 times.
|
214 | const QString key = top.value(0).toString(); |
| 165 |
3/3✓ Branch 31 → 32 taken 214 times.
✓ Branch 32 → 33 taken 214 times.
✓ Branch 33 → 34 taken 214 times.
|
214 | usage.insert(key, top.value(1).toInt()); |
| 166 |
1/1✓ Branch 35 → 36 taken 214 times.
|
214 | QSqlQuery card(db); |
| 167 |
1/1✓ Branch 42 → 43 taken 214 times.
|
642 | card.prepare(QStringLiteral("SELECT %1 FROM card_addresses a JOIN cards c ON c.id = a.card_id " |
| 168 | "JOIN address_books b ON b.id = c.book_id WHERE a.address_key = ? AND b.show_in_all = 1 " | ||
| 169 | "ORDER BY (c.sync_state = 'legacy'), c.id LIMIT 1") | ||
| 170 |
1/1✓ Branch 41 → 42 taken 214 times.
|
642 | .arg(QLatin1String(kRowColumns))); |
| 171 |
2/2✓ Branch 48 → 49 taken 214 times.
✓ Branch 49 → 50 taken 214 times.
|
214 | card.addBindValue(sqlValue(key)); |
| 172 |
7/8✓ Branch 52 → 53 taken 214 times.
✓ Branch 53 → 54 taken 214 times.
✗ Branch 53 → 57 not taken.
✓ Branch 54 → 55 taken 214 times.
✓ Branch 55 → 56 taken 97 times.
✓ Branch 55 → 57 taken 117 times.
✓ Branch 58 → 59 taken 97 times.
✓ Branch 58 → 67 taken 117 times.
|
214 | if (card.exec() && card.next()) { |
| 173 |
1/1✓ Branch 59 → 60 taken 97 times.
|
97 | Row row = rowFrom(card, false); |
| 174 | 97 | row.useCount = usage.value(key); | |
| 175 |
1/2✓ Branch 62 → 63 taken 97 times.
✗ Branch 62 → 65 not taken.
|
97 | if (!seenCards.contains(row.key)) { |
| 176 |
1/1✓ Branch 63 → 64 taken 97 times.
|
97 | seenCards.insert(row.key); |
| 177 |
1/1✓ Branch 64 → 65 taken 97 times.
|
97 | rows.append(row); |
| 178 | } | ||
| 179 | 97 | } else { | |
| 180 |
1/1✓ Branch 67 → 68 taken 117 times.
|
117 | collectedKeys.insert(key); |
| 181 | } | ||
| 182 | 214 | } | |
| 183 |
2/2✓ Branch 75 → 76 taken 55 times.
✓ Branch 75 → 84 taken 820 times.
|
875 | if (!collectedKeys.isEmpty()) |
| 184 |
3/3✓ Branch 77 → 78 taken 55 times.
✓ Branch 78 → 79 taken 55 times.
✓ Branch 79 → 80 taken 55 times.
|
55 | rows += collectedRows(db, QString(), collectedKeys); |
| 185 |
1/1✓ Branch 84 → 85 taken 875 times.
|
875 | const QStringList words = filterWords(filter); |
| 186 | 875 | QList<Row> filtered; | |
| 187 |
4/4✓ Branch 85 → 86 taken 875 times.
✓ Branch 86 → 87 taken 875 times.
✓ Branch 110 → 88 taken 214 times.
✓ Branch 110 → 111 taken 875 times.
|
1089 | for (const Row &row : rows) { |
| 188 |
2/2✓ Branch 93 → 94 taken 214 times.
✓ Branch 94 → 95 taken 214 times.
|
428 | const QString haystack = ContactIndex::foldForSort(row.displayName + QLatin1Char(' ') + row.primaryAddress + |
| 189 |
3/3✓ Branch 95 → 96 taken 214 times.
✓ Branch 96 → 97 taken 214 times.
✓ Branch 97 → 98 taken 214 times.
|
428 | QLatin1Char(' ') + row.organization); |
| 190 |
2/3✓ Branch 104 → 105 taken 214 times.
✓ Branch 105 → 106 taken 214 times.
✗ Branch 105 → 107 not taken.
|
214 | if (std::all_of(words.cbegin(), words.cend(), [&](const QString &w) { return haystack.contains(w); })) |
| 191 |
1/1✓ Branch 106 → 107 taken 214 times.
|
214 | filtered.append(row); |
| 192 | 214 | } | |
| 193 |
3/3✓ Branch 111 → 112 taken 875 times.
✓ Branch 112 → 113 taken 875 times.
✓ Branch 113 → 114 taken 875 times.
|
875 | std::sort(filtered.begin(), filtered.end(), [](const Row &a, const Row &b) { |
| 194 |
1/2✗ Branch 3 → 4 not taken.
✓ Branch 3 → 7 taken 283 times.
|
283 | return a.sortKey == b.sortKey ? a.key < b.key : sortKeyLess(a.sortKey, b.sortKey); |
| 195 | }); | ||
| 196 | 875 | return filtered; | |
| 197 | 875 | } | |
| 198 | |||
| 199 | 7 | QList<Row> sliceAfter(const QList<Row> &rows, int limit, const QString &afterKey, qint64 afterId, | |
| 200 | const QString &afterRowKey, bool keyed) { | ||
| 201 | 7 | QList<Row> result; | |
| 202 |
2/2✓ Branch 27 → 4 taken 207 times.
✓ Branch 27 → 28 taken 6 times.
|
213 | for (const Row &row : rows) { |
| 203 |
2/2✓ Branch 5 → 6 taken 105 times.
✓ Branch 5 → 21 taken 102 times.
|
207 | if (keyed) { |
| 204 |
2/3✓ Branch 6 → 7 taken 105 times.
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 105 times.
|
105 | if (sortKeyLess(row.sortKey, afterKey)) |
| 205 | ✗ | continue; | |
| 206 |
1/2✓ Branch 10 → 11 taken 105 times.
✗ Branch 10 → 21 not taken.
|
105 | if (row.sortKey == afterKey) { |
| 207 | // F-61 (SPRINT-104): collected rows have cardId == 0 — the former | ||
| 208 | // cardId cursor excluded every later row with an equal sort key, | ||
| 209 | // so equal-name collected addresses were skipped at the page | ||
| 210 | // boundary. They tie-break by their stable key instead; card rows | ||
| 211 | // keep the id comparison. | ||
| 212 |
2/4✓ Branch 11 → 12 taken 105 times.
✗ Branch 11 → 13 not taken.
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 16 taken 105 times.
|
105 | if (row.cardId > 0 || afterId > 0) { |
| 213 | ✗ | if (row.cardId <= afterId) | |
| 214 | ✗ | continue; | |
| 215 |
2/2✓ Branch 18 → 19 taken 100 times.
✓ Branch 18 → 20 taken 5 times.
|
105 | } else if (row.key <= afterRowKey) { |
| 216 | 100 | continue; | |
| 217 | } | ||
| 218 | } | ||
| 219 | } | ||
| 220 |
1/1✓ Branch 21 → 22 taken 107 times.
|
107 | result.append(row); |
| 221 |
2/2✓ Branch 23 → 24 taken 1 time.
✓ Branch 23 → 25 taken 106 times.
|
107 | if (result.size() >= limit) |
| 222 | 1 | break; | |
| 223 | } | ||
| 224 | 7 | return result; | |
| 225 | ✗ | } | |
| 226 | |||
| 227 | } // namespace | ||
| 228 | |||
| 229 |
3/3✓ Branch 4 → 5 taken 19833 times.
✓ Branch 5 → 6 taken 19833 times.
✓ Branch 6 → 7 taken 19833 times.
|
19833 | QString cardKey(qint64 bookId, const QString &uid) { return QString::number(bookId) + QLatin1Char('/') + uid; } |
| 230 | |||
| 231 | 2161 | bool splitKey(const QString &key, qint64 *bookId, QString *uid) { | |
| 232 | 2161 | const int slash = key.indexOf(QLatin1Char('/')); | |
| 233 |
6/7✓ Branch 5 → 6 taken 2107 times.
✓ Branch 5 → 9 taken 54 times.
✓ Branch 7 → 8 taken 2107 times.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 2107 times.
✓ Branch 11 → 12 taken 54 times.
✓ Branch 11 → 13 taken 2107 times.
|
2161 | if (slash <= 0 || key.startsWith(QLatin1String("collected:"))) |
| 234 | 54 | return false; | |
| 235 | 2107 | bool ok = false; | |
| 236 |
2/2✓ Branch 13 → 14 taken 2107 times.
✓ Branch 14 → 15 taken 2107 times.
|
2107 | const qint64 book = key.left(slash).toLongLong(&ok); |
| 237 |
1/2✗ Branch 16 → 17 not taken.
✓ Branch 16 → 18 taken 2107 times.
|
2107 | if (!ok) |
| 238 | ✗ | return false; | |
| 239 |
1/2✓ Branch 18 → 19 taken 2107 times.
✗ Branch 18 → 20 not taken.
|
2107 | if (bookId) |
| 240 | 2107 | *bookId = book; | |
| 241 |
1/2✓ Branch 20 → 21 taken 2107 times.
✗ Branch 20 → 25 not taken.
|
2107 | if (uid) |
| 242 |
1/1✓ Branch 21 → 22 taken 2107 times.
|
2107 | *uid = key.mid(slash + 1); |
| 243 | 2107 | return true; | |
| 244 | } | ||
| 245 | |||
| 246 | 978 | int count(QSqlDatabase db, const View &view, const QString &filter) { | |
| 247 |
2/2✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 12 taken 977 times.
|
978 | if (view.kind == ViewKind::Collected) |
| 248 |
2/2✓ Branch 4 → 5 taken 1 time.
✓ Branch 5 → 6 taken 1 time.
|
1 | return int(collectedRows(db, filter).size()); |
| 249 |
2/2✓ Branch 12 → 13 taken 1 time.
✓ Branch 12 → 20 taken 976 times.
|
977 | if (view.kind == ViewKind::Frequent) |
| 250 |
2/2✓ Branch 13 → 14 taken 1 time.
✓ Branch 14 → 15 taken 1 time.
|
1 | return int(frequentRows(db, filter).size()); |
| 251 | 976 | QVariantMap binds; | |
| 252 |
1/1✓ Branch 20 → 21 taken 976 times.
|
976 | QString where = viewCondition(view, &binds); |
| 253 |
1/1✓ Branch 21 → 22 taken 976 times.
|
976 | const QString words = filterCondition(filter, &binds); |
| 254 |
2/2✓ Branch 23 → 24 taken 4 times.
✓ Branch 23 → 33 taken 972 times.
|
976 | if (!words.isEmpty()) |
| 255 |
2/2✓ Branch 27 → 28 taken 4 times.
✓ Branch 28 → 29 taken 4 times.
|
4 | where += QStringLiteral(" AND ") + words; |
| 256 |
1/1✓ Branch 33 → 34 taken 976 times.
|
976 | QSqlQuery q(db); |
| 257 |
2/2✓ Branch 37 → 38 taken 976 times.
✓ Branch 38 → 39 taken 976 times.
|
976 | q.prepare(QStringLiteral("SELECT COUNT(*) FROM cards c JOIN address_books b ON b.id = c.book_id WHERE ") + where); |
| 258 |
1/1✓ Branch 42 → 43 taken 976 times.
|
976 | bindAll(&q, binds); |
| 259 |
9/12✓ Branch 43 → 44 taken 976 times.
✓ Branch 44 → 45 taken 975 times.
✓ Branch 44 → 50 taken 1 time.
✓ Branch 45 → 46 taken 975 times.
✓ Branch 46 → 47 taken 975 times.
✗ Branch 46 → 50 not taken.
✓ Branch 47 → 48 taken 975 times.
✓ Branch 48 → 49 taken 975 times.
✓ Branch 52 → 53 taken 975 times.
✓ Branch 52 → 54 taken 1 time.
✗ Branch 89 → 90 not taken.
✗ Branch 89 → 91 not taken.
|
976 | return q.exec() && q.next() ? q.value(0).toInt() : 0; |
| 260 | 976 | } | |
| 261 | |||
| 262 | 1088 | QList<Row> page(QSqlDatabase db, const View &view, const QString &filter, int limit, const QString &afterKey, | |
| 263 | qint64 afterId, const QString &afterRowKey) { | ||
| 264 |
4/4✓ Branch 2 → 3 taken 1074 times.
✓ Branch 2 → 5 taken 14 times.
✓ Branch 4 → 5 taken 1 time.
✓ Branch 4 → 6 taken 1073 times.
|
1088 | const bool keyed = afterId > 0 || !afterKey.isEmpty(); |
| 265 |
2/2✓ Branch 7 → 8 taken 7 times.
✓ Branch 7 → 17 taken 1081 times.
|
1088 | if (view.kind == ViewKind::Collected) |
| 266 |
3/3✓ Branch 9 → 10 taken 7 times.
✓ Branch 10 → 11 taken 7 times.
✓ Branch 11 → 12 taken 7 times.
|
14 | return sliceAfter(collectedRows(db, filter), limit, afterKey, afterId, afterRowKey, keyed); |
| 267 |
2/2✓ Branch 17 → 18 taken 54 times.
✓ Branch 17 → 29 taken 1027 times.
|
1081 | if (view.kind == ViewKind::Frequent) |
| 268 |
6/13✗ Branch 18 → 19 not taken.
✓ Branch 18 → 20 taken 54 times.
✓ Branch 20 → 21 taken 54 times.
✓ Branch 21 → 22 taken 54 times.
✓ Branch 22 → 23 taken 54 times.
✓ Branch 24 → 25 taken 54 times.
✗ Branch 24 → 26 not taken.
✓ Branch 26 → 27 taken 54 times.
✗ Branch 26 → 28 not taken.
✗ Branch 136 → 137 not taken.
✗ Branch 136 → 138 not taken.
✗ Branch 140 → 141 not taken.
✗ Branch 140 → 142 not taken.
|
108 | return keyed ? QList<Row>{} : frequentRows(db, filter).mid(0, limit); |
| 269 | 1027 | QVariantMap binds; | |
| 270 |
1/1✓ Branch 29 → 30 taken 1027 times.
|
1027 | QString where = viewCondition(view, &binds); |
| 271 |
1/1✓ Branch 30 → 31 taken 1027 times.
|
1027 | const QString words = filterCondition(filter, &binds); |
| 272 |
2/2✓ Branch 32 → 33 taken 113 times.
✓ Branch 32 → 42 taken 914 times.
|
1027 | if (!words.isEmpty()) |
| 273 |
2/2✓ Branch 36 → 37 taken 113 times.
✓ Branch 37 → 38 taken 113 times.
|
113 | where += QStringLiteral(" AND ") + words; |
| 274 |
6/6✓ Branch 42 → 43 taken 9 times.
✓ Branch 42 → 46 taken 1018 times.
✓ Branch 49 → 50 taken 1018 times.
✓ Branch 49 → 51 taken 9 times.
✓ Branch 51 → 52 taken 9 times.
✓ Branch 51 → 53 taken 1018 times.
|
2054 | const QString sortColumn = view.byFamilyName ? QStringLiteral("c.sort_family") : QStringLiteral("c.sort_display"); |
| 275 |
2/2✓ Branch 53 → 54 taken 14 times.
✓ Branch 53 → 80 taken 1013 times.
|
1027 | if (keyed) { |
| 276 |
2/2✓ Branch 58 → 59 taken 14 times.
✓ Branch 59 → 60 taken 14 times.
|
28 | where += QStringLiteral(" AND (%1 > :afterKey OR (%1 = :afterKey AND c.id > :afterId))").arg(sortColumn); |
| 277 |
1/1✓ Branch 67 → 68 taken 14 times.
|
28 | binds.insert(QStringLiteral(":afterKey"), afterKey); |
| 278 |
1/1✓ Branch 75 → 76 taken 14 times.
|
28 | binds.insert(QStringLiteral(":afterId"), afterId); |
| 279 | } | ||
| 280 | // A search matches few cards: one table scan and sorting the matches is | ||
| 281 | // cheaper than walking the sort index with a row read per entry ("+" | ||
| 282 | // keeps SQLite off the index). Without a search the index walk stops | ||
| 283 | // after `limit` rows (G10, A/B in SPRINT-97 §16.5). | ||
| 284 |
3/3✓ Branch 81 → 82 taken 914 times.
✓ Branch 81 → 83 taken 113 times.
✓ Branch 85 → 86 taken 113 times.
|
1027 | const QString order = words.isEmpty() ? sortColumn : QLatin1Char('+') + sortColumn; |
| 285 |
1/1✓ Branch 86 → 87 taken 1027 times.
|
1027 | QSqlQuery q(db); |
| 286 |
1/1✓ Branch 92 → 93 taken 1027 times.
|
3081 | q.prepare(QStringLiteral("SELECT %1 FROM cards c JOIN address_books b ON b.id = c.book_id WHERE %2 " |
| 287 | "ORDER BY %3, c.id LIMIT :limit") | ||
| 288 |
1/1✓ Branch 91 → 92 taken 1027 times.
|
2054 | .arg(QLatin1String(kRowColumns), where, order)); |
| 289 |
1/1✓ Branch 96 → 97 taken 1027 times.
|
1027 | bindAll(&q, binds); |
| 290 |
2/2✓ Branch 99 → 100 taken 1027 times.
✓ Branch 103 → 104 taken 1027 times.
|
2054 | q.bindValue(QStringLiteral(":limit"), sqlValue(limit)); |
| 291 | 1027 | QList<Row> rows; | |
| 292 |
3/3✓ Branch 108 → 109 taken 1027 times.
✓ Branch 109 → 110 taken 1026 times.
✓ Branch 109 → 117 taken 1 time.
|
1027 | if (q.exec()) |
| 293 |
3/3✓ Branch 115 → 116 taken 6405 times.
✓ Branch 116 → 111 taken 5379 times.
✓ Branch 116 → 117 taken 1026 times.
|
6405 | while (q.next()) |
| 294 |
2/2✓ Branch 111 → 112 taken 5379 times.
✓ Branch 112 → 113 taken 5379 times.
|
5379 | rows.append(rowFrom(q, view.byFamilyName)); |
| 295 | 1027 | return rows; | |
| 296 | 1027 | } | |
| 297 | |||
| 298 | 36 | QList<Row> rowsForCards(QSqlDatabase db, const View &view, const QString &filter, const QList<qint64> &cardIds) { | |
| 299 | 36 | QList<Row> rows; | |
| 300 |
3/6✓ Branch 3 → 4 taken 36 times.
✗ Branch 3 → 5 not taken.
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 36 times.
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 36 times.
|
36 | if (cardIds.isEmpty() || view.kind == ViewKind::Collected) |
| 301 | ✗ | return rows; | |
| 302 |
1/2✗ Branch 9 → 10 not taken.
✓ Branch 9 → 29 taken 36 times.
|
36 | if (view.kind == ViewKind::Frequent) { |
| 303 | ✗ | const QSet<qint64> wanted(cardIds.cbegin(), cardIds.cend()); | |
| 304 | ✗ | for (const Row &row : frequentRows(db, filter)) | |
| 305 | ✗ | if (wanted.contains(row.cardId)) | |
| 306 | ✗ | rows.append(row); | |
| 307 | ✗ | return rows; | |
| 308 | ✗ | } | |
| 309 | 36 | QVariantMap binds; | |
| 310 |
1/1✓ Branch 29 → 30 taken 36 times.
|
36 | QString where = viewCondition(view, &binds); |
| 311 |
1/1✓ Branch 30 → 31 taken 36 times.
|
36 | const QString words = filterCondition(filter, &binds); |
| 312 |
1/2✗ Branch 32 → 33 not taken.
✓ Branch 32 → 42 taken 36 times.
|
36 | if (!words.isEmpty()) |
| 313 | ✗ | where += QStringLiteral(" AND ") + words; | |
| 314 | 36 | QStringList ids; | |
| 315 |
2/2✓ Branch 50 → 44 taken 36 times.
✓ Branch 50 → 51 taken 36 times.
|
72 | for (qint64 id : cardIds) |
| 316 |
2/2✓ Branch 45 → 46 taken 36 times.
✓ Branch 46 → 47 taken 36 times.
|
36 | ids << QString::number(id); |
| 317 |
1/1✓ Branch 51 → 52 taken 36 times.
|
36 | QSqlQuery q(db); |
| 318 |
1/1✓ Branch 60 → 61 taken 36 times.
|
108 | q.prepare(QStringLiteral("SELECT %1 FROM cards c JOIN address_books b ON b.id = c.book_id WHERE %2 AND c.id IN (%3)") |
| 319 |
2/2✓ Branch 57 → 58 taken 36 times.
✓ Branch 59 → 60 taken 36 times.
|
72 | .arg(QLatin1String(kRowColumns), where, ids.join(QLatin1Char(',')))); |
| 320 |
1/1✓ Branch 65 → 66 taken 36 times.
|
36 | bindAll(&q, binds); |
| 321 |
2/3✓ Branch 66 → 67 taken 36 times.
✓ Branch 67 → 68 taken 36 times.
✗ Branch 67 → 75 not taken.
|
36 | if (q.exec()) |
| 322 |
3/3✓ Branch 73 → 74 taken 72 times.
✓ Branch 74 → 69 taken 36 times.
✓ Branch 74 → 75 taken 36 times.
|
72 | while (q.next()) |
| 323 |
2/2✓ Branch 69 → 70 taken 36 times.
✓ Branch 70 → 71 taken 36 times.
|
36 | rows.append(rowFrom(q, view.byFamilyName)); |
| 324 | 36 | return rows; | |
| 325 | 36 | } | |
| 326 | |||
| 327 | 2642 | QHash<qint64, int> bookCounts(QSqlDatabase db) { | |
| 328 | 2642 | QHash<qint64, int> counts; | |
| 329 |
1/1✓ Branch 2 → 3 taken 2642 times.
|
2642 | QSqlQuery q(db); |
| 330 |
2/3✓ Branch 6 → 7 taken 2642 times.
✓ Branch 9 → 10 taken 2642 times.
✗ Branch 9 → 21 not taken.
|
2642 | if (q.exec(QStringLiteral("SELECT book_id, COUNT(*) FROM cards WHERE kind <> 'group' GROUP BY book_id"))) |
| 331 |
3/3✓ Branch 19 → 20 taken 4016 times.
✓ Branch 20 → 11 taken 1374 times.
✓ Branch 20 → 21 taken 2642 times.
|
4016 | while (q.next()) |
| 332 |
5/5✓ Branch 11 → 12 taken 1374 times.
✓ Branch 12 → 13 taken 1374 times.
✓ Branch 13 → 14 taken 1374 times.
✓ Branch 14 → 15 taken 1374 times.
✓ Branch 15 → 16 taken 1374 times.
|
1374 | counts.insert(q.value(0).toLongLong(), q.value(1).toInt()); |
| 333 | 2642 | return counts; | |
| 334 | 2642 | } | |
| 335 | |||
| 336 | 821 | QHash<qint64, int> ungroupedCounts(QSqlDatabase db) { | |
| 337 | 821 | QHash<qint64, int> counts; | |
| 338 |
1/1✓ Branch 2 → 3 taken 821 times.
|
821 | QSqlQuery q(db); |
| 339 |
1/1✓ Branch 9 → 10 taken 821 times.
|
2463 | if (q.exec(QStringLiteral("SELECT c.book_id, COUNT(*) FROM cards c WHERE c.kind <> 'group' AND %1 GROUP BY c.book_id") |
| 340 |
2/3✓ Branch 8 → 9 taken 821 times.
✓ Branch 13 → 14 taken 821 times.
✗ Branch 13 → 25 not taken.
|
2463 | .arg(QLatin1String(kUngrouped)))) |
| 341 |
3/3✓ Branch 23 → 24 taken 1121 times.
✓ Branch 24 → 15 taken 300 times.
✓ Branch 24 → 25 taken 821 times.
|
1121 | while (q.next()) |
| 342 |
5/5✓ Branch 15 → 16 taken 300 times.
✓ Branch 16 → 17 taken 300 times.
✓ Branch 17 → 18 taken 300 times.
✓ Branch 18 → 19 taken 300 times.
✓ Branch 19 → 20 taken 300 times.
|
300 | counts.insert(q.value(0).toLongLong(), q.value(1).toInt()); |
| 343 | 821 | return counts; | |
| 344 | 821 | } | |
| 345 | |||
| 346 |
2/2✓ Branch 3 → 4 taken 902 times.
✓ Branch 4 → 5 taken 902 times.
|
902 | int allCount(QSqlDatabase db) { return count(db, View{}, QString()); } |
| 347 | |||
| 348 |
2/2✓ Branch 3 → 4 taken 820 times.
✓ Branch 4 → 5 taken 820 times.
|
820 | int frequentCount(QSqlDatabase db) { return int(frequentRows(db, QString()).size()); } |
| 349 | |||
| 350 | 827 | int collectedCount(QSqlDatabase db) { | |
| 351 |
1/1✓ Branch 2 → 3 taken 827 times.
|
827 | QSqlQuery q(db); |
| 352 |
2/8✓ Branch 14 → 15 taken 827 times.
✗ Branch 14 → 16 not taken.
✓ Branch 16 → 17 taken 827 times.
✗ Branch 16 → 22 not taken.
✗ Branch 26 → 27 not taken.
✗ Branch 26 → 28 not taken.
✗ Branch 30 → 31 not taken.
✗ Branch 30 → 32 not taken.
|
1654 | if (q.exec(QStringLiteral("SELECT COUNT(*) FROM collected_addresses WHERE address_key NOT IN " |
| 353 |
3/5✓ Branch 6 → 7 taken 827 times.
✓ Branch 7 → 8 taken 827 times.
✗ Branch 7 → 11 not taken.
✓ Branch 12 → 13 taken 827 times.
✗ Branch 12 → 14 not taken.
|
2481 | "(SELECT address_key FROM card_addresses)")) && |
| 354 |
2/3✓ Branch 8 → 9 taken 827 times.
✓ Branch 9 → 10 taken 827 times.
✗ Branch 9 → 11 not taken.
|
827 | q.next()) |
| 355 |
2/2✓ Branch 17 → 18 taken 827 times.
✓ Branch 18 → 19 taken 827 times.
|
827 | return q.value(0).toInt(); |
| 356 | ✗ | return 0; | |
| 357 | 827 | } | |
| 358 | |||
| 359 | // Recipients match by name (the folded display name) or address — not by | ||
| 360 | // notes, organisation or postal address, which the list search covers | ||
| 361 | // (G10: a short column per row, A/B in SPRINT-97 §16.5). | ||
| 362 | 28 | QList<Completion> completions(QSqlDatabase db, const QString &fragment, int limit) { | |
| 363 | 28 | QList<Completion> result; | |
| 364 |
1/1✓ Branch 2 → 3 taken 28 times.
|
28 | const QString folded = ContactIndex::foldForSort(fragment); |
| 365 |
2/2✓ Branch 3 → 4 taken 28 times.
✓ Branch 4 → 5 taken 28 times.
|
28 | const QString lower = fragment.trimmed().toLower(); |
| 366 |
2/6✗ Branch 7 → 8 not taken.
✓ Branch 7 → 11 taken 28 times.
✗ Branch 9 → 10 not taken.
✗ Branch 9 → 11 not taken.
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 15 taken 28 times.
|
28 | if (folded.isEmpty() && lower.isEmpty()) |
| 367 | ✗ | return result; | |
| 368 | 28 | QSet<QString> seen; | |
| 369 |
1/1✓ Branch 16 → 17 taken 28 times.
|
28 | QSqlQuery q(db); |
| 370 |
1/1✓ Branch 20 → 21 taken 28 times.
|
28 | q.prepare(QStringLiteral( |
| 371 | "SELECT c.book_id, c.uid, c.display_name, a.address, a.address_key, b.display_name, " | ||
| 372 | "COALESCE(u.use_count, 0), (b.account_id = '' AND b.path = 'local') " | ||
| 373 | "FROM card_addresses a JOIN cards c ON c.id = a.card_id JOIN address_books b ON b.id = c.book_id " | ||
| 374 | "LEFT JOIN address_usage u ON u.address_key = a.address_key " | ||
| 375 | "WHERE b.use_for_completion = 1 AND (c.sort_display LIKE :name ESCAPE '\\' OR a.address_key LIKE :address ESCAPE '\\') " | ||
| 376 | "ORDER BY COALESCE(u.use_count, 0) DESC, (c.sort_display LIKE :prefix ESCAPE '\\') DESC, c.sort_display, a.position " | ||
| 377 | "LIMIT :limit")); | ||
| 378 |
3/3✓ Branch 24 → 25 taken 28 times.
✓ Branch 26 → 27 taken 28 times.
✓ Branch 30 → 31 taken 28 times.
|
56 | q.bindValue(QStringLiteral(":name"), sqlValue(likePattern(folded))); |
| 379 |
3/3✓ Branch 37 → 38 taken 28 times.
✓ Branch 39 → 40 taken 28 times.
✓ Branch 43 → 44 taken 28 times.
|
56 | q.bindValue(QStringLiteral(":address"), sqlValue(likePattern(lower))); |
| 380 |
4/4✓ Branch 50 → 51 taken 28 times.
✓ Branch 51 → 52 taken 28 times.
✓ Branch 53 → 54 taken 28 times.
✓ Branch 57 → 58 taken 28 times.
|
56 | q.bindValue(QStringLiteral(":prefix"), sqlValue(likePattern(folded).mid(1))); |
| 381 |
2/2✓ Branch 66 → 67 taken 28 times.
✓ Branch 70 → 71 taken 28 times.
|
56 | q.bindValue(QStringLiteral(":limit"), sqlValue(limit * 3)); |
| 382 |
3/3✓ Branch 75 → 76 taken 28 times.
✓ Branch 76 → 77 taken 27 times.
✓ Branch 76 → 134 taken 1 time.
|
28 | if (q.exec()) { |
| 383 |
6/7✓ Branch 127 → 128 taken 42 times.
✓ Branch 128 → 129 taken 15 times.
✓ Branch 128 → 132 taken 27 times.
✓ Branch 130 → 131 taken 15 times.
✗ Branch 130 → 132 not taken.
✓ Branch 133 → 78 taken 15 times.
✓ Branch 133 → 134 taken 27 times.
|
42 | while (q.next() && result.size() < limit) { |
| 384 |
2/2✓ Branch 78 → 79 taken 15 times.
✓ Branch 79 → 80 taken 15 times.
|
15 | const QString key = q.value(4).toString(); |
| 385 |
1/2✗ Branch 82 → 83 not taken.
✓ Branch 82 → 84 taken 15 times.
|
15 | if (seen.contains(key)) |
| 386 | ✗ | continue; | |
| 387 |
1/1✓ Branch 84 → 85 taken 15 times.
|
15 | seen.insert(key); |
| 388 | 15 | Completion completion; | |
| 389 |
5/5✓ Branch 85 → 86 taken 15 times.
✓ Branch 86 → 87 taken 15 times.
✓ Branch 87 → 88 taken 15 times.
✓ Branch 88 → 89 taken 15 times.
✓ Branch 89 → 90 taken 15 times.
|
15 | completion.key = cardKey(q.value(0).toLongLong(), q.value(1).toString()); |
| 390 |
2/2✓ Branch 95 → 96 taken 15 times.
✓ Branch 96 → 97 taken 15 times.
|
15 | completion.uid = q.value(1).toString(); |
| 391 |
2/2✓ Branch 100 → 101 taken 15 times.
✓ Branch 101 → 102 taken 15 times.
|
15 | completion.name = q.value(2).toString(); |
| 392 |
2/2✓ Branch 105 → 106 taken 15 times.
✓ Branch 106 → 107 taken 15 times.
|
15 | completion.address = q.value(3).toString(); |
| 393 |
2/2✓ Branch 110 → 111 taken 15 times.
✓ Branch 111 → 112 taken 15 times.
|
15 | completion.bookName = q.value(5).toString(); |
| 394 |
2/2✓ Branch 115 → 116 taken 15 times.
✓ Branch 116 → 117 taken 15 times.
|
15 | completion.useCount = q.value(6).toInt(); |
| 395 |
1/1✓ Branch 118 → 119 taken 15 times.
|
15 | completion.avatarColor = ContactIndex::avatarColorIndex(completion.uid); |
| 396 |
1/1✓ Branch 119 → 120 taken 15 times.
|
15 | result.append(completion); |
| 397 | 15 | } | |
| 398 | } | ||
| 399 |
1/1✓ Branch 134 → 135 taken 28 times.
|
28 | QSqlQuery collected(db); |
| 400 |
1/1✓ Branch 138 → 139 taken 28 times.
|
28 | collected.prepare(QStringLiteral( |
| 401 | "SELECT ca.address, ca.address_key, ca.display_name, COALESCE(u.use_count, 0) FROM collected_addresses ca " | ||
| 402 | "LEFT JOIN address_usage u ON u.address_key = ca.address_key " | ||
| 403 | "WHERE (ca.address_key LIKE :address ESCAPE '\\' OR lower(ca.display_name) LIKE :address ESCAPE '\\') " | ||
| 404 | "ORDER BY COALESCE(u.use_count, 0) DESC LIMIT :limit")); | ||
| 405 |
3/3✓ Branch 142 → 143 taken 28 times.
✓ Branch 144 → 145 taken 28 times.
✓ Branch 148 → 149 taken 28 times.
|
56 | collected.bindValue(QStringLiteral(":address"), sqlValue(likePattern(lower))); |
| 406 |
2/2✓ Branch 156 → 157 taken 28 times.
✓ Branch 160 → 161 taken 28 times.
|
56 | collected.bindValue(QStringLiteral(":limit"), sqlValue(limit)); |
| 407 | 28 | QList<Completion> extra; | |
| 408 |
3/3✓ Branch 165 → 166 taken 28 times.
✓ Branch 166 → 167 taken 27 times.
✓ Branch 166 → 207 taken 1 time.
|
28 | if (collected.exec()) { |
| 409 |
3/3✓ Branch 205 → 206 taken 31 times.
✓ Branch 206 → 168 taken 4 times.
✓ Branch 206 → 207 taken 27 times.
|
31 | while (collected.next()) { |
| 410 |
2/2✓ Branch 168 → 169 taken 4 times.
✓ Branch 169 → 170 taken 4 times.
|
4 | const QString key = collected.value(1).toString(); |
| 411 |
1/2✗ Branch 172 → 173 not taken.
✓ Branch 172 → 174 taken 4 times.
|
4 | if (seen.contains(key)) |
| 412 | ✗ | continue; | |
| 413 |
1/1✓ Branch 174 → 175 taken 4 times.
|
4 | seen.insert(key); |
| 414 | 4 | Completion completion; | |
| 415 |
1/1✓ Branch 178 → 179 taken 4 times.
|
4 | completion.key = QStringLiteral("collected:") + key; |
| 416 |
2/2✓ Branch 183 → 184 taken 4 times.
✓ Branch 184 → 185 taken 4 times.
|
4 | completion.address = collected.value(0).toString(); |
| 417 |
2/2✓ Branch 188 → 189 taken 4 times.
✓ Branch 189 → 190 taken 4 times.
|
4 | completion.name = collected.value(2).toString(); |
| 418 | 4 | completion.collected = true; | |
| 419 |
2/2✓ Branch 193 → 194 taken 4 times.
✓ Branch 194 → 195 taken 4 times.
|
4 | completion.useCount = collected.value(3).toInt(); |
| 420 |
1/1✓ Branch 196 → 197 taken 4 times.
|
4 | completion.avatarColor = ContactIndex::avatarColorIndex(key); |
| 421 |
1/1✓ Branch 197 → 198 taken 4 times.
|
4 | extra.append(completion); |
| 422 | 4 | } | |
| 423 | } | ||
| 424 | // Usage ranks across both sources. | ||
| 425 |
1/1✓ Branch 207 → 208 taken 28 times.
|
28 | result += extra; |
| 426 |
3/3✓ Branch 208 → 209 taken 28 times.
✓ Branch 209 → 210 taken 28 times.
✓ Branch 210 → 211 taken 28 times.
|
28 | std::stable_sort(result.begin(), result.end(), |
| 427 | 3 | [](const Completion &a, const Completion &b) { return a.useCount > b.useCount; }); | |
| 428 | // S97.5.4: groups by name prefix, ahead of single addresses. | ||
| 429 | 28 | QList<Completion> groupsFound; | |
| 430 |
6/6✓ Branch 211 → 212 taken 28 times.
✓ Branch 212 → 213 taken 28 times.
✓ Branch 214 → 215 taken 28 times.
✓ Branch 215 → 216 taken 28 times.
✓ Branch 284 → 217 taken 12 times.
✓ Branch 284 → 285 taken 28 times.
|
40 | for (const Group &group : groups(db)) { |
| 431 |
8/12✓ Branch 218 → 219 taken 12 times.
✓ Branch 219 → 220 taken 12 times.
✓ Branch 220 → 221 taken 8 times.
✓ Branch 220 → 223 taken 4 times.
✗ Branch 222 → 223 not taken.
✓ Branch 222 → 224 taken 8 times.
✓ Branch 225 → 226 taken 12 times.
✗ Branch 225 → 227 not taken.
✓ Branch 227 → 228 taken 4 times.
✓ Branch 227 → 229 taken 8 times.
✗ Branch 476 → 477 not taken.
✗ Branch 476 → 478 not taken.
|
12 | if (!ContactIndex::foldForSort(group.name).startsWith(folded) || folded.isEmpty()) |
| 432 | 4 | continue; | |
| 433 |
1/1✓ Branch 229 → 230 taken 8 times.
|
8 | QSqlQuery book(db); |
| 434 |
1/1✓ Branch 233 → 234 taken 8 times.
|
8 | book.prepare(QStringLiteral("SELECT display_name FROM address_books WHERE id = ? AND use_for_completion = 1")); |
| 435 |
1/1✓ Branch 238 → 239 taken 8 times.
|
8 | book.addBindValue(group.bookId); |
| 436 |
5/8✓ Branch 240 → 241 taken 8 times.
✓ Branch 241 → 242 taken 8 times.
✗ Branch 241 → 244 not taken.
✓ Branch 242 → 243 taken 8 times.
✗ Branch 243 → 244 not taken.
✓ Branch 243 → 245 taken 8 times.
✗ Branch 246 → 247 not taken.
✓ Branch 246 → 248 taken 8 times.
|
8 | if (!book.exec() || !book.next()) |
| 437 | ✗ | continue; | |
| 438 | 8 | Completion completion; | |
| 439 |
2/2✓ Branch 252 → 253 taken 8 times.
✓ Branch 254 → 255 taken 8 times.
|
24 | completion.key = QStringLiteral("group:%1/%2").arg(group.bookId).arg(group.name); |
| 440 | 8 | completion.name = group.name; | |
| 441 |
2/2✓ Branch 261 → 262 taken 8 times.
✓ Branch 262 → 263 taken 8 times.
|
8 | completion.bookName = book.value(0).toString(); |
| 442 | 8 | completion.group = true; | |
| 443 |
2/2✓ Branch 266 → 267 taken 8 times.
✓ Branch 267 → 268 taken 8 times.
|
8 | completion.members = groupMembers(db, group.bookId, group.name); |
| 444 |
1/1✓ Branch 271 → 272 taken 8 times.
|
8 | completion.avatarColor = ContactIndex::avatarColorIndex(completion.key); |
| 445 |
1/2✓ Branch 273 → 274 taken 8 times.
✗ Branch 273 → 275 not taken.
|
8 | if (!completion.members.isEmpty()) |
| 446 |
1/1✓ Branch 274 → 275 taken 8 times.
|
8 | groupsFound.append(completion); |
| 447 | 36 | } | |
| 448 |
2/2✓ Branch 286 → 287 taken 28 times.
✓ Branch 287 → 288 taken 28 times.
|
28 | return (groupsFound + result).mid(0, limit); |
| 449 | 28 | } | |
| 450 | |||
| 451 | 104 | QList<Duplicate> duplicatesOf(QSqlDatabase db, const QString &key) { | |
| 452 | 104 | QList<Duplicate> result; | |
| 453 | 104 | qint64 bookId = 0; | |
| 454 | 104 | QString uid; | |
| 455 |
2/3✓ Branch 2 → 3 taken 104 times.
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 104 times.
|
104 | if (!splitKey(key, &bookId, &uid)) |
| 456 | ✗ | return result; | |
| 457 |
1/1✓ Branch 5 → 6 taken 104 times.
|
104 | QSqlQuery q(db); |
| 458 |
1/1✓ Branch 9 → 10 taken 104 times.
|
104 | q.prepare(QStringLiteral( |
| 459 | "SELECT DISTINCT o.book_id, o.uid, o.display_name, b.display_name, (b.account_id = '' AND b.path = 'local') " | ||
| 460 | "FROM cards c JOIN cards o ON o.id <> c.id AND o.kind NOT IN ('group', 'family') " | ||
| 461 | "JOIN address_books b ON b.id = o.book_id " | ||
| 462 | "WHERE c.book_id = :book AND c.uid = :uid AND c.kind NOT IN ('group', 'family') AND (" | ||
| 463 | " (c.sort_display <> '' AND o.sort_display = c.sort_display) OR" | ||
| 464 | " EXISTS (SELECT 1 FROM card_addresses a JOIN card_addresses oa ON oa.address_key = a.address_key" | ||
| 465 | " WHERE a.card_id = c.id AND oa.card_id = o.id)) " | ||
| 466 | "ORDER BY (o.book_id = :book) DESC, o.sort_display LIMIT 10")); | ||
| 467 |
1/1✓ Branch 17 → 18 taken 104 times.
|
208 | q.bindValue(QStringLiteral(":book"), bookId); |
| 468 |
2/2✓ Branch 23 → 24 taken 104 times.
✓ Branch 27 → 28 taken 104 times.
|
208 | q.bindValue(QStringLiteral(":uid"), sqlValue(uid)); |
| 469 |
2/3✓ Branch 32 → 33 taken 104 times.
✓ Branch 33 → 34 taken 104 times.
✗ Branch 33 → 63 not taken.
|
104 | if (q.exec()) |
| 470 |
3/3✓ Branch 61 → 62 taken 106 times.
✓ Branch 62 → 35 taken 2 times.
✓ Branch 62 → 63 taken 104 times.
|
106 | while (q.next()) |
| 471 |
3/3✓ Branch 35 → 36 taken 2 times.
✓ Branch 36 → 37 taken 2 times.
✓ Branch 37 → 38 taken 2 times.
|
6 | result.append({cardKey(q.value(0).toLongLong(), q.value(1).toString()), q.value(2).toString(), |
| 472 |
4/7✓ Branch 43 → 44 taken 2 times.
✓ Branch 46 → 47 taken 2 times.
✓ Branch 49 → 50 taken 2 times.
✓ Branch 53 → 54 taken 2 times.
✗ Branch 53 → 55 not taken.
✗ Branch 104 → 105 not taken.
✗ Branch 104 → 106 not taken.
|
8 | q.value(4).toBool() ? QString() : q.value(3).toString(), q.value(0).toLongLong() == bookId}); |
| 473 | 104 | return result; | |
| 474 | 112 | } | |
| 475 | |||
| 476 | 1327 | Lookup lookup(QSqlDatabase db, const QString &address) { | |
| 477 | 1327 | Lookup result; | |
| 478 |
1/1✓ Branch 2 → 3 taken 1327 times.
|
1327 | const QString key = ContactIndex::addressKey(address); |
| 479 |
1/2✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 1327 times.
|
1327 | if (key.isEmpty()) |
| 480 | ✗ | return result; | |
| 481 |
1/1✓ Branch 6 → 7 taken 1327 times.
|
1327 | QSqlQuery q(db); |
| 482 |
1/1✓ Branch 10 → 11 taken 1327 times.
|
1327 | q.prepare(QStringLiteral("SELECT c.book_id, c.uid, c.display_name, b.display_name FROM card_addresses a " |
| 483 | "JOIN cards c ON c.id = a.card_id JOIN address_books b ON b.id = c.book_id " | ||
| 484 | "WHERE a.address_key = ? ORDER BY (c.sync_state = 'legacy'), b.show_in_all DESC, c.id LIMIT 1")); | ||
| 485 |
2/2✓ Branch 15 → 16 taken 1327 times.
✓ Branch 16 → 17 taken 1327 times.
|
1327 | q.addBindValue(sqlValue(key)); |
| 486 |
7/8✓ Branch 19 → 20 taken 1327 times.
✓ Branch 20 → 21 taken 1327 times.
✗ Branch 20 → 24 not taken.
✓ Branch 21 → 22 taken 1327 times.
✓ Branch 22 → 23 taken 82 times.
✓ Branch 22 → 24 taken 1245 times.
✓ Branch 25 → 26 taken 82 times.
✓ Branch 25 → 48 taken 1245 times.
|
1327 | if (q.exec() && q.next()) { |
| 487 | 82 | result.known = true; | |
| 488 |
2/2✓ Branch 26 → 27 taken 82 times.
✓ Branch 27 → 28 taken 82 times.
|
82 | result.bookId = q.value(0).toLongLong(); |
| 489 |
2/2✓ Branch 29 → 30 taken 82 times.
✓ Branch 30 → 31 taken 82 times.
|
82 | result.uid = q.value(1).toString(); |
| 490 |
1/1✓ Branch 34 → 35 taken 82 times.
|
82 | result.key = cardKey(result.bookId, result.uid); |
| 491 |
2/2✓ Branch 37 → 38 taken 82 times.
✓ Branch 38 → 39 taken 82 times.
|
82 | result.displayName = q.value(2).toString(); |
| 492 |
2/2✓ Branch 42 → 43 taken 82 times.
✓ Branch 43 → 44 taken 82 times.
|
82 | result.bookName = q.value(3).toString(); |
| 493 | 82 | return result; | |
| 494 | } | ||
| 495 |
1/1✓ Branch 48 → 49 taken 1245 times.
|
1245 | QSqlQuery collected(db); |
| 496 |
1/1✓ Branch 52 → 53 taken 1245 times.
|
1245 | collected.prepare(QStringLiteral("SELECT display_name FROM collected_addresses WHERE address_key = ?")); |
| 497 |
2/2✓ Branch 57 → 58 taken 1245 times.
✓ Branch 58 → 59 taken 1245 times.
|
1245 | collected.addBindValue(sqlValue(key)); |
| 498 |
7/8✓ Branch 61 → 62 taken 1245 times.
✓ Branch 62 → 63 taken 1245 times.
✗ Branch 62 → 66 not taken.
✓ Branch 63 → 64 taken 1245 times.
✓ Branch 64 → 65 taken 23 times.
✓ Branch 64 → 66 taken 1222 times.
✓ Branch 67 → 68 taken 23 times.
✓ Branch 67 → 82 taken 1222 times.
|
1245 | if (collected.exec() && collected.next()) { |
| 499 | 23 | result.collected = true; | |
| 500 |
1/1✓ Branch 71 → 72 taken 23 times.
|
23 | result.key = QStringLiteral("collected:") + key; |
| 501 |
2/2✓ Branch 76 → 77 taken 23 times.
✓ Branch 77 → 78 taken 23 times.
|
23 | result.displayName = collected.value(0).toString(); |
| 502 | } | ||
| 503 | 1245 | return result; | |
| 504 | 1327 | } | |
| 505 | |||
| 506 | 57 | QList<Lookup> sameBookOwners(QSqlDatabase db, qint64 bookId, const QString &address, const QString &exceptKey) { | |
| 507 | 57 | QList<Lookup> owners; | |
| 508 |
1/1✓ Branch 2 → 3 taken 57 times.
|
57 | QSqlQuery q(db); |
| 509 |
1/1✓ Branch 6 → 7 taken 57 times.
|
57 | q.prepare(QStringLiteral("SELECT c.book_id, c.uid, c.display_name FROM card_addresses a JOIN cards c ON c.id = a.card_id " |
| 510 | "WHERE a.address_key = ? AND c.book_id = ? AND c.sync_state <> 'legacy'")); | ||
| 511 |
3/3✓ Branch 10 → 11 taken 57 times.
✓ Branch 12 → 13 taken 57 times.
✓ Branch 13 → 14 taken 57 times.
|
57 | q.addBindValue(sqlValue(ContactIndex::addressKey(address))); |
| 512 |
2/2✓ Branch 19 → 20 taken 57 times.
✓ Branch 20 → 21 taken 57 times.
|
57 | q.addBindValue(sqlValue(bookId)); |
| 513 |
2/3✓ Branch 23 → 24 taken 57 times.
✓ Branch 24 → 25 taken 57 times.
✗ Branch 24 → 49 not taken.
|
57 | if (q.exec()) { |
| 514 |
3/3✓ Branch 47 → 48 taken 94 times.
✓ Branch 48 → 26 taken 37 times.
✓ Branch 48 → 49 taken 57 times.
|
94 | while (q.next()) { |
| 515 | 37 | Lookup owner; | |
| 516 | 37 | owner.known = true; | |
| 517 |
2/2✓ Branch 26 → 27 taken 37 times.
✓ Branch 27 → 28 taken 37 times.
|
37 | owner.bookId = q.value(0).toLongLong(); |
| 518 |
2/2✓ Branch 29 → 30 taken 37 times.
✓ Branch 30 → 31 taken 37 times.
|
37 | owner.uid = q.value(1).toString(); |
| 519 |
1/1✓ Branch 34 → 35 taken 37 times.
|
37 | owner.key = cardKey(owner.bookId, owner.uid); |
| 520 |
2/2✓ Branch 37 → 38 taken 37 times.
✓ Branch 38 → 39 taken 37 times.
|
37 | owner.displayName = q.value(2).toString(); |
| 521 |
2/2✓ Branch 43 → 44 taken 13 times.
✓ Branch 43 → 45 taken 24 times.
|
37 | if (owner.key != exceptKey) |
| 522 |
1/1✓ Branch 44 → 45 taken 13 times.
|
13 | owners.append(owner); |
| 523 | 37 | } | |
| 524 | } | ||
| 525 | 57 | return owners; | |
| 526 | 57 | } | |
| 527 | |||
| 528 | 3 | QStringList cardAddresses(QSqlDatabase db, qint64 cardId) { | |
| 529 | 3 | QStringList addresses; | |
| 530 |
1/1✓ Branch 2 → 3 taken 3 times.
|
3 | QSqlQuery q(db); |
| 531 |
1/1✓ Branch 6 → 7 taken 3 times.
|
3 | q.prepare(QStringLiteral("SELECT address FROM card_addresses WHERE card_id = ? ORDER BY preferred DESC, position")); |
| 532 |
2/2✓ Branch 11 → 12 taken 3 times.
✓ Branch 12 → 13 taken 3 times.
|
3 | q.addBindValue(sqlValue(cardId)); |
| 533 |
2/3✓ Branch 15 → 16 taken 3 times.
✓ Branch 16 → 17 taken 3 times.
✗ Branch 16 → 26 not taken.
|
3 | if (q.exec()) |
| 534 |
6/6✓ Branch 18 → 19 taken 6 times.
✓ Branch 19 → 20 taken 6 times.
✓ Branch 20 → 21 taken 6 times.
✓ Branch 24 → 25 taken 9 times.
✓ Branch 25 → 18 taken 6 times.
✓ Branch 25 → 26 taken 3 times.
|
9 | while (q.next()) addresses.append(q.value(0).toString()); |
| 535 | 3 | return addresses; | |
| 536 | 3 | } | |
| 537 | |||
| 538 | } // namespace ContactQueries | ||
| 539 |