data/ContactPeopleFilter.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "ContactPeopleFilter.h" | ||
| 2 | |||
| 3 | #include "data/ContactIndex.h" | ||
| 4 | #include "data/GeoData.h" | ||
| 5 | #include "data/PlaceGazetteer.h" | ||
| 6 | |||
| 7 | #include <QRegularExpression> | ||
| 8 | #include <cmath> | ||
| 9 | |||
| 10 | namespace ContactPeopleFilter { | ||
| 11 | |||
| 12 | namespace { | ||
| 13 | |||
| 14 | 10 | QString like(const QString &value) { | |
| 15 | 10 | QString escaped = value; | |
| 16 |
1/1✓ Branch 8 → 9 taken 10 times.
|
10 | escaped.replace(QLatin1Char('\\'), QStringLiteral("\\\\")); |
| 17 |
1/1✓ Branch 16 → 17 taken 10 times.
|
10 | escaped.replace(QLatin1Char('%'), QStringLiteral("\\%")); |
| 18 |
1/1✓ Branch 24 → 25 taken 10 times.
|
10 | escaped.replace(QLatin1Char('_'), QStringLiteral("\\_")); |
| 19 |
2/2✓ Branch 31 → 32 taken 10 times.
✓ Branch 32 → 33 taken 10 times.
|
30 | return QLatin1Char('%') + escaped + QLatin1Char('%'); |
| 20 | 10 | } | |
| 21 | |||
| 22 | // The condition of one term; "" when the term is not a people term or has | ||
| 23 | // no usable value (then it stays a full-text word). | ||
| 24 | 16 | QString condition(const QString &key, const QString &value, QVariantMap *binds) { | |
| 25 |
2/2✓ Branch 6 → 7 taken 16 times.
✓ Branch 7 → 8 taken 16 times.
|
32 | const QString name = QStringLiteral(":p%1").arg(binds->size()); |
| 26 | 10 | const auto bind = [&](const QVariant &v) { binds->insert(name, v); }; | |
| 27 |
2/2✓ Branch 12 → 13 taken 3 times.
✓ Branch 12 → 42 taken 13 times.
|
16 | if (key == QLatin1String("has")) { |
| 28 |
5/6✓ Branch 15 → 16 taken 2 times.
✓ Branch 15 → 19 taken 1 time.
✗ Branch 18 → 19 not taken.
✓ Branch 18 → 20 taken 2 times.
✓ Branch 21 → 22 taken 1 time.
✓ Branch 21 → 27 taken 2 times.
|
3 | if (value == QLatin1String("relationship") || value == QLatin1String("beziehung")) |
| 29 | 1 | return QStringLiteral("(EXISTS (SELECT 1 FROM card_relations r WHERE r.card_id = c.id AND r.role = 'relation') OR " | |
| 30 | "EXISTS (SELECT 1 FROM card_relations r WHERE r.target_uid = c.uid AND r.role = 'relation'))"); | ||
| 31 |
2/6✗ Branch 29 → 30 not taken.
✓ Branch 29 → 33 taken 2 times.
✗ Branch 32 → 33 not taken.
✗ Branch 32 → 34 not taken.
✓ Branch 35 → 36 taken 2 times.
✗ Branch 35 → 41 not taken.
|
2 | if (value == QLatin1String("place") || value == QLatin1String("ort")) |
| 32 | 2 | return QStringLiteral("EXISTS (SELECT 1 FROM card_places p WHERE p.card_id = c.id AND p.lat IS NOT NULL)"); | |
| 33 | ✗ | return {}; | |
| 34 | } | ||
| 35 |
1/2✗ Branch 43 → 44 not taken.
✓ Branch 43 → 45 taken 13 times.
|
13 | if (value.isEmpty()) |
| 36 | ✗ | return {}; | |
| 37 |
2/2✓ Branch 47 → 48 taken 6 times.
✓ Branch 47 → 61 taken 7 times.
|
13 | if (key == QLatin1String("related")) { |
| 38 |
2/2✓ Branch 48 → 49 taken 6 times.
✓ Branch 50 → 51 taken 6 times.
|
6 | bind(like(value)); |
| 39 | 12 | return QStringLiteral("(EXISTS (SELECT 1 FROM card_relations r WHERE r.card_id = c.id AND r.role = 'relation'" | |
| 40 | " AND r.target_name LIKE %1 ESCAPE '\\') OR EXISTS (SELECT 1 FROM card_relations r" | ||
| 41 | " JOIN cards o ON o.id = r.card_id WHERE r.target_uid = c.uid AND r.role = 'relation'" | ||
| 42 |
1/1✓ Branch 57 → 58 taken 6 times.
|
6 | " AND o.display_name LIKE %1 ESCAPE '\\'))").arg(name); |
| 43 | } | ||
| 44 |
2/2✓ Branch 63 → 64 taken 1 time.
✓ Branch 63 → 77 taken 6 times.
|
7 | if (key == QLatin1String("via")) { |
| 45 |
2/2✓ Branch 64 → 65 taken 1 time.
✓ Branch 66 → 67 taken 1 time.
|
1 | bind(like(value)); |
| 46 | 2 | return QStringLiteral("EXISTS (SELECT 1 FROM card_relations r WHERE r.card_id = c.id AND r.role = 'via'" | |
| 47 |
1/1✓ Branch 73 → 74 taken 1 time.
|
1 | " AND r.target_name LIKE %1 ESCAPE '\\')").arg(name); |
| 48 | } | ||
| 49 |
2/2✓ Branch 79 → 80 taken 2 times.
✓ Branch 79 → 119 taken 4 times.
|
6 | if (key == QLatin1String("met")) { |
| 50 |
2/2✓ Branch 80 → 81 taken 2 times.
✓ Branch 82 → 83 taken 2 times.
|
2 | bind(like(value)); |
| 51 | // The year of the meeting is in the card's search text (S103.1.4). | ||
| 52 |
4/7✓ Branch 85 → 86 taken 1 time.
✓ Branch 85 → 98 taken 1 time.
✓ Branch 87 → 88 taken 1 time.
✗ Branch 87 → 98 not taken.
✓ Branch 92 → 93 taken 1 time.
✗ Branch 258 → 259 not taken.
✗ Branch 258 → 260 not taken.
|
3 | static const QRegularExpression year(QStringLiteral(R"(^\d{4}$)")); |
| 53 |
4/4✓ Branch 99 → 100 taken 2 times.
✓ Branch 100 → 101 taken 2 times.
✓ Branch 102 → 103 taken 1 time.
✓ Branch 102 → 111 taken 1 time.
|
2 | if (year.match(value).hasMatch()) |
| 54 |
1/1✓ Branch 107 → 108 taken 1 time.
|
2 | return QStringLiteral("c.search_text LIKE %1 ESCAPE '\\'").arg(name); |
| 55 | 2 | return QStringLiteral("EXISTS (SELECT 1 FROM card_places p WHERE p.card_id = c.id AND p.kind = 'met'" | |
| 56 |
1/1✓ Branch 115 → 116 taken 1 time.
|
1 | " AND (p.text LIKE %1 ESCAPE '\\' OR p.place_name LIKE %1 ESCAPE '\\'))").arg(name); |
| 57 | } | ||
| 58 |
2/2✓ Branch 121 → 122 taken 1 time.
✓ Branch 121 → 137 taken 3 times.
|
4 | if (key == QLatin1String("interest")) { |
| 59 |
3/3✓ Branch 122 → 123 taken 1 time.
✓ Branch 123 → 124 taken 1 time.
✓ Branch 125 → 126 taken 1 time.
|
1 | bind(like(ContactIndex::foldForSort(value))); |
| 60 |
1/1✓ Branch 133 → 134 taken 1 time.
|
2 | return QStringLiteral("c.search_text LIKE %1 ESCAPE '\\'").arg(name); |
| 61 | } | ||
| 62 |
1/2✓ Branch 139 → 140 taken 3 times.
✗ Branch 139 → 208 not taken.
|
3 | if (key == QLatin1String("near")) { |
| 63 |
1/1✓ Branch 140 → 141 taken 3 times.
|
3 | const std::shared_ptr<const GeoData> data = GeoData::builtIn(); |
| 64 |
3/7✓ Branch 142 → 143 taken 3 times.
✗ Branch 142 → 146 not taken.
✓ Branch 145 → 147 taken 3 times.
✓ Branch 147 → 148 taken 3 times.
✗ Branch 147 → 149 not taken.
✗ Branch 295 → 296 not taken.
✗ Branch 295 → 297 not taken.
|
3 | const PlaceGazetteer::Place place = data ? PlaceGazetteer::locateText(*data, value) : PlaceGazetteer::Place{}; |
| 65 |
1/2✗ Branch 150 → 151 not taken.
✓ Branch 150 → 156 taken 3 times.
|
3 | if (!place.isValid()) |
| 66 | ✗ | return QStringLiteral("0"); // an unknown place matches nobody | |
| 67 | // About 50 km: a box of ±0.45° latitude and the matching longitude. | ||
| 68 | 3 | const double lat = place.position.latitude, lon = place.position.longitude; | |
| 69 | 3 | const double dLon = 0.45 / qMax(0.1, std::cos(lat * M_PI / 180.0)); | |
| 70 |
2/2✓ Branch 161 → 162 taken 3 times.
✓ Branch 162 → 163 taken 3 times.
|
6 | binds->insert(name + QStringLiteral("a"), lat - 0.45); |
| 71 |
2/2✓ Branch 171 → 172 taken 3 times.
✓ Branch 172 → 173 taken 3 times.
|
6 | binds->insert(name + QStringLiteral("b"), lat + 0.45); |
| 72 |
2/2✓ Branch 181 → 182 taken 3 times.
✓ Branch 182 → 183 taken 3 times.
|
6 | binds->insert(name + QStringLiteral("c"), lon - dLon); |
| 73 |
2/2✓ Branch 191 → 192 taken 3 times.
✓ Branch 192 → 193 taken 3 times.
|
6 | binds->insert(name + QStringLiteral("d"), lon + dLon); |
| 74 | 6 | return QStringLiteral("EXISTS (SELECT 1 FROM card_places p WHERE p.card_id = c.id AND p.lat BETWEEN %1a AND %1b" | |
| 75 |
1/1✓ Branch 201 → 202 taken 3 times.
|
3 | " AND p.lon BETWEEN %1c AND %1d)").arg(name); |
| 76 | 3 | } | |
| 77 | ✗ | return {}; | |
| 78 | 16 | } | |
| 79 | |||
| 80 | } // namespace | ||
| 81 | |||
| 82 | 2040 | Split split(const QString &filter, QVariantMap *binds) { | |
| 83 | static const QRegularExpression term( | ||
| 84 | 66 | QStringLiteral(R"re((?:^|\s)(related|via|met|near|interest|has):(?:"([^"]*)"|(\S+)))re"), | |
| 85 |
4/7✓ Branch 2 → 3 taken 33 times.
✓ Branch 2 → 15 taken 2007 times.
✓ Branch 4 → 5 taken 33 times.
✗ Branch 4 → 15 not taken.
✓ Branch 9 → 10 taken 33 times.
✗ Branch 94 → 95 not taken.
✗ Branch 94 → 96 not taken.
|
2106 | QRegularExpression::CaseInsensitiveOption); |
| 86 | 2040 | Split result; | |
| 87 | 2040 | QString rest = filter; | |
| 88 |
1/1✓ Branch 17 → 18 taken 2040 times.
|
2040 | auto it = term.globalMatch(filter); |
| 89 | // S106-168: matched terms are cut out by position — replacing their text | ||
| 90 | // everywhere also cut a shorter term out of a longer one. | ||
| 91 | 2040 | QList<QPair<qsizetype, qsizetype>> consumed; | |
| 92 |
3/3✓ Branch 64 → 65 taken 2056 times.
✓ Branch 65 → 19 taken 16 times.
✓ Branch 65 → 66 taken 2040 times.
|
2056 | while (it.hasNext()) { |
| 93 |
1/1✓ Branch 19 → 20 taken 16 times.
|
16 | const QRegularExpressionMatch match = it.next(); |
| 94 |
2/2✓ Branch 20 → 21 taken 16 times.
✓ Branch 21 → 22 taken 16 times.
|
16 | const QString key = match.captured(1).toLower(); |
| 95 |
6/6✓ Branch 23 → 24 taken 16 times.
✓ Branch 25 → 26 taken 13 times.
✓ Branch 25 → 27 taken 3 times.
✓ Branch 26 → 28 taken 13 times.
✓ Branch 27 → 28 taken 3 times.
✓ Branch 28 → 29 taken 16 times.
|
16 | const QString value = (match.captured(2).isNull() ? match.captured(3) : match.captured(2)).trimmed(); |
| 96 |
4/4✓ Branch 33 → 34 taken 3 times.
✓ Branch 33 → 35 taken 13 times.
✓ Branch 34 → 36 taken 3 times.
✓ Branch 36 → 37 taken 16 times.
|
16 | const QString sql = condition(key, key == QLatin1String("has") ? value.toLower() : value, binds); |
| 97 |
1/2✗ Branch 39 → 40 not taken.
✓ Branch 39 → 41 taken 16 times.
|
16 | if (sql.isEmpty()) |
| 98 | ✗ | continue; | |
| 99 |
1/1✓ Branch 41 → 42 taken 16 times.
|
16 | result.conditions.append(sql); |
| 100 |
3/3✓ Branch 42 → 43 taken 16 times.
✓ Branch 43 → 44 taken 16 times.
✓ Branch 45 → 46 taken 16 times.
|
16 | consumed.append({match.capturedStart(0), match.capturedLength(0)}); |
| 101 | 16 | } | |
| 102 |
3/3✓ Branch 77 → 78 taken 2056 times.
✓ Branch 78 → 67 taken 16 times.
✓ Branch 78 → 79 taken 2040 times.
|
2056 | for (auto range = consumed.crbegin(); range != consumed.crend(); ++range) |
| 103 |
4/4✓ Branch 70 → 71 taken 16 times.
✓ Branch 71 → 72 taken 16 times.
✓ Branch 72 → 73 taken 16 times.
✓ Branch 75 → 76 taken 16 times.
|
16 | rest.replace(range->first, range->second, QStringLiteral(" ")); |
| 104 |
1/1✓ Branch 79 → 80 taken 2040 times.
|
2040 | result.rest = rest.simplified(); |
| 105 | 2040 | return result; | |
| 106 | 2040 | } | |
| 107 | |||
| 108 | } // namespace ContactPeopleFilter | ||
| 109 |