Merge lp:~boiko/history-service/rtm-fix_group_thread_matching into lp:history-service/rtm-14.09

Proposed by Gustavo Pichorim Boiko
Status: Merged
Approved by: Tiago Salem Herrmann
Approved revision: 173
Merged at revision: 173
Proposed branch: lp:~boiko/history-service/rtm-fix_group_thread_matching
Merge into: lp:history-service/rtm-14.09
Diff against target: 397 lines (+159/-51)
6 files modified
plugins/sqlite/sqlitehistoryeventview.cpp (+14/-2)
plugins/sqlite/sqlitehistoryplugin.cpp (+6/-22)
plugins/sqlite/sqlitehistoryplugin.h (+1/-1)
plugins/sqlite/sqlitehistorythreadview.cpp (+14/-2)
plugins/sqlite/tests/SqlitePluginTest.cpp (+31/-13)
src/tests/EventViewTest.cpp (+93/-11)
To merge this branch: bzr merge lp:~boiko/history-service/rtm-fix_group_thread_matching
Reviewer Review Type Date Requested Status
Tiago Salem Herrmann (community) Approve
Review via email: mp+247917@code.launchpad.net

Commit message

Use QSqlQuery::bindValue() to pass filter arguments to the query to prevent errors.

Description of the change

Use QSqlQuery::bindValue() to pass filter arguments to the query to prevent errors.

== Checklist ==
Are there any related MPs required for this MP to build/function as expected? Please list.
No

Is your branch in sync with latest trunk (e.g. bzr pull lp:trunk -> no changes)
Yes

Did you perform an exploratory manual test run of your code change and any related functionality on device or emulator?
Yes

Did you successfully run all tests found in your component's Test Plan (https://wiki.ubuntu.com/Process/Merges/TestPlan/<package-name>) on device or emulator?
Yes

If you changed the UI, was the change specified/approved by design?
N/A

If you changed UI labels, did you update the pot file?
N/A

If you changed the packaging (debian), did you add a core-dev as a reviewer to this MP?
N/A

To post a comment you must log in.
Revision history for this message
Tiago Salem Herrmann (tiagosh) wrote :

Looks good.

--Checklist--
Did you perform an exploratory manual test run of the code change and any related functionality on
device or emulator?
Yes

Did CI run pass? If not, please explain why.
No, it does not run for rtm branches.

Have you checked that submitter has accurately filled out the submitter checklist and has taken no shortcut?
Yes

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'plugins/sqlite/sqlitehistoryeventview.cpp'
2--- plugins/sqlite/sqlitehistoryeventview.cpp 2015-01-23 20:21:34 +0000
3+++ plugins/sqlite/sqlitehistoryeventview.cpp 2015-01-28 23:16:22 +0000
4@@ -38,7 +38,8 @@
5 mQuery.setForwardOnly(true);
6
7 // FIXME: validate the filter
8- QString condition = mPlugin->filterToString(filter);
9+ QVariantMap filterValues;
10+ QString condition = mPlugin->filterToString(filter, filterValues);
11 QString order;
12 if (!sort.sortField().isNull()) {
13 order = QString("ORDER BY %1 %2").arg(sort.sortField(), sort.sortOrder() == Qt::AscendingOrder ? "ASC" : "DESC");
14@@ -48,7 +49,18 @@
15 QString queryText = QString("CREATE TEMP TABLE %1 AS ").arg(mTemporaryTable);
16 queryText += mPlugin->sqlQueryForEvents(type, condition, order);
17
18- if (!mQuery.exec(queryText)) {
19+ if (!mQuery.prepare(queryText)) {
20+ mValid = false;
21+ Q_EMIT Invalidated();
22+ qCritical() << "Error:" << mQuery.lastError() << mQuery.lastQuery();
23+ return;
24+ }
25+
26+ Q_FOREACH(const QString &key, filterValues.keys()) {
27+ mQuery.bindValue(key, filterValues[key]);
28+ }
29+
30+ if (!mQuery.exec()) {
31 mValid = false;
32 Q_EMIT Invalidated();
33 qCritical() << "Error:" << mQuery.lastError() << mQuery.lastQuery();
34
35=== modified file 'plugins/sqlite/sqlitehistoryplugin.cpp'
36--- plugins/sqlite/sqlitehistoryplugin.cpp 2015-01-25 13:25:56 +0000
37+++ plugins/sqlite/sqlitehistoryplugin.cpp 2015-01-28 23:16:22 +0000
38@@ -662,7 +662,7 @@
39 return QDateTime(timestamp.date(), timestamp.time(), Qt::UTC).toLocalTime().toString("yyyy-MM-ddTHH:mm:ss.zzz");
40 }
41
42-QString SQLiteHistoryPlugin::filterToString(const History::Filter &filter, const QString &propertyPrefix) const
43+QString SQLiteHistoryPlugin::filterToString(const History::Filter &filter, QVariantMap &bindValues, const QString &propertyPrefix) const
44 {
45 QString result;
46 History::Filters filters;
47@@ -690,7 +690,7 @@
48 count = filters.count();
49 for (int i = 0; i < count; ++i) {
50 // run recursively through the inner filters
51- result += QString("(%1)").arg(filterToString(filters[i], propertyPrefix));
52+ result += QString("(%1)").arg(filterToString(filters[i], bindValues, propertyPrefix));
53 if (i != count-1) {
54 result += linking;
55 }
56@@ -698,36 +698,20 @@
57 result += " )";
58 break;
59 default:
60- // FIXME: remove the toString() functionality or replace it by a better implementation
61 if (filterProperty.isEmpty() || filterValue.isNull()) {
62 break;
63 }
64
65- switch (filterValue.type()) {
66- case QVariant::String:
67- // FIXME: need to escape strings
68- // wrap strings
69- value = QString("'%1'").arg(escapeFilterValue(filterValue.toString()));
70- break;
71- case QVariant::Bool:
72- value = filterValue.toBool() ? "1" : "0";
73- break;
74- case QVariant::Int:
75- value = QString::number(filterValue.toInt());
76- break;
77- case QVariant::Double:
78- value = QString::number(filterValue.toDouble());
79- break;
80- default:
81- value = filterValue.toString();
82- }
83+ QString bindId = QString(":filterValue%1").arg(bindValues.count());
84
85 QString propertyName = propertyPrefix.isNull() ? filterProperty : QString("%1.%2").arg(propertyPrefix, filterProperty);
86 // FIXME: need to check for other match flags and multiple match flags
87 if (filter.matchFlags() & History::MatchContains) {
88+ // FIXME: maybe we should use QString("%1 LIKE '\%'||%2'\%'").arg(bindId) ?? needs more time for investigating
89 result = QString("%1 LIKE '\%%2\%' ESCAPE '\\'").arg(propertyName, escapeFilterValue(filterValue.toString()));
90 } else {
91- result = QString("%1=%2").arg(propertyName, value);
92+ result = QString("%1=%2").arg(propertyName, bindId);
93+ bindValues[bindId] = filterValue;
94 }
95 }
96
97
98=== modified file 'plugins/sqlite/sqlitehistoryplugin.h'
99--- plugins/sqlite/sqlitehistoryplugin.h 2015-01-24 19:34:40 +0000
100+++ plugins/sqlite/sqlitehistoryplugin.h 2015-01-28 23:16:22 +0000
101@@ -80,7 +80,7 @@
102
103 static QString toLocalTimeString(const QDateTime &timestamp);
104
105- QString filterToString(const History::Filter &filter, const QString &propertyPrefix = QString::null) const;
106+ QString filterToString(const History::Filter &filter, QVariantMap &bindValues, const QString &propertyPrefix = QString::null) const;
107 QString escapeFilterValue(const QString &value) const;
108 };
109
110
111=== modified file 'plugins/sqlite/sqlitehistorythreadview.cpp'
112--- plugins/sqlite/sqlitehistorythreadview.cpp 2015-01-23 20:21:34 +0000
113+++ plugins/sqlite/sqlitehistorythreadview.cpp 2015-01-28 23:16:22 +0000
114@@ -39,7 +39,8 @@
115 mQuery.setForwardOnly(true);
116
117 // FIXME: validate the filter
118- QString condition = mPlugin->filterToString(filter);
119+ QVariantMap filterValues;
120+ QString condition = mPlugin->filterToString(filter, filterValues);
121 QString order;
122 if (!sort.sortField().isNull()) {
123 order = QString("ORDER BY %1 %2").arg(sort.sortField(), sort.sortOrder() == Qt::AscendingOrder ? "ASC" : "DESC");
124@@ -49,8 +50,19 @@
125 QString queryText = QString("CREATE TEMP TABLE %1 AS ").arg(mTemporaryTable);
126 queryText += mPlugin->sqlQueryForThreads(type, condition, order);
127
128+ if (!mQuery.prepare(queryText)) {
129+ qCritical() << "Error:" << mQuery.lastError() << mQuery.lastQuery();
130+ mValid = false;
131+ Q_EMIT Invalidated();
132+ return;
133+ }
134+
135+ Q_FOREACH(const QString &key, filterValues.keys()) {
136+ mQuery.bindValue(key, filterValues[key]);
137+ }
138+
139 // create the temporary table
140- if (!mQuery.exec(queryText)) {
141+ if (!mQuery.exec()) {
142 qCritical() << "Error:" << mQuery.lastError() << mQuery.lastQuery();
143 mValid = false;
144 Q_EMIT Invalidated();
145
146=== modified file 'plugins/sqlite/tests/SqlitePluginTest.cpp'
147--- plugins/sqlite/tests/SqlitePluginTest.cpp 2015-01-25 13:25:56 +0000
148+++ plugins/sqlite/tests/SqlitePluginTest.cpp 2015-01-28 23:16:22 +0000
149@@ -718,35 +718,41 @@
150 void SqlitePluginTest::testFilterToString_data()
151 {
152 QTest::addColumn<QVariantMap>("filterProperties");
153+ QTest::addColumn<QVariantMap>("filterValues");
154 QTest::addColumn<QString>("propertyPrefix");
155 QTest::addColumn<QString>("resultString");
156
157 History::Filter filter;
158+ QVariantMap filterValues;
159 filter.setFilterProperty("testProperty");
160 filter.setFilterValue("stringValue");
161- QTest::newRow("simple string filter") << filter.properties() << QString() << "testProperty='stringValue'";
162-
163- filter.setFilterValue("escaped\\0\\n%");
164- QTest::newRow("check that strings are escaped") << filter.properties() << QString() << "testProperty='escaped\\\\0\\\\n\\%'";
165+ filterValues[":filterValue0"] = filter.filterValue();
166+ QTest::newRow("simple string filter") << filter.properties() << filterValues << QString() << "testProperty=:filterValue0";
167
168 filter.setFilterValue(12345);
169- QTest::newRow("simple integer filter") << filter.properties() << QString() << "testProperty=12345";
170+ filterValues[":filterValue0"] = filter.filterValue();
171+ QTest::newRow("simple integer filter") << filter.properties() << filterValues << QString() << "testProperty=:filterValue0";
172
173 filter.setFilterValue(true);
174- QTest::newRow("simple true boolean filter") << filter.properties() << QString() << "testProperty=1";
175+ filterValues[":filterValue0"] = filter.filterValue();
176+ QTest::newRow("simple true boolean filter") << filter.properties() << filterValues << QString() << "testProperty=:filterValue0";
177
178 filter.setFilterValue(false);
179- QTest::newRow("simple false boolean filter") << filter.properties() << QString() << "testProperty=0";
180+ filterValues[":filterValue0"] = filter.filterValue();
181+ QTest::newRow("simple false boolean filter") << filter.properties() << filterValues << QString() << "testProperty=:filterValue0";
182
183 filter.setFilterValue(12345);
184- QTest::newRow("filter with a prefix") << filter.properties() << QString("prefix") << "prefix.testProperty=12345";
185+ filterValues[":filterValue0"] = filter.filterValue();
186+ QTest::newRow("filter with a prefix") << filter.properties() << filterValues << QString("prefix") << "prefix.testProperty=:filterValue0";
187
188 filter.setMatchFlags(History::MatchContains);
189 filter.setFilterValue("partialString");
190- QTest::newRow("match contains") << filter.properties() << QString() << "testProperty LIKE '\%partialString\%' ESCAPE '\\'";
191+ filterValues.clear();
192+ QTest::newRow("match contains") << filter.properties() << filterValues << QString() << "testProperty LIKE '\%partialString\%' ESCAPE '\\'";
193
194 filter.setFilterValue("%");
195- QTest::newRow("partial match escaped") << filter.properties() << QString() << "testProperty LIKE '\%\\\%\%' ESCAPE '\\'";
196+ filterValues.clear();
197+ QTest::newRow("partial match escaped") << filter.properties() << filterValues << QString() << "testProperty LIKE '\%\\\%\%' ESCAPE '\\'";
198
199 History::IntersectionFilter intersectionFilter;
200 filter.setMatchFlags(History::MatchFlags());
201@@ -756,7 +762,11 @@
202 intersectionFilter.append(filter);
203 filter.setFilterValue("a string");
204 intersectionFilter.append(filter);
205- QTest::newRow("intersection filter") << intersectionFilter.properties() << QString() << "( (testProperty=12345) AND (testProperty=1) AND (testProperty='a string') )";
206+ filterValues.clear();
207+ filterValues[":filterValue0"] = 12345;
208+ filterValues[":filterValue1"] = true;
209+ filterValues[":filterValue2"] = "a string";
210+ QTest::newRow("intersection filter") << intersectionFilter.properties() << filterValues << QString() << "( (testProperty=:filterValue0) AND (testProperty=:filterValue1) AND (testProperty=:filterValue2) )";
211
212 History::UnionFilter unionFilter;
213 filter.setFilterValue(12345);
214@@ -765,17 +775,25 @@
215 unionFilter.append(filter);
216 filter.setFilterValue("a string");
217 unionFilter.append(filter);
218- QTest::newRow("union filter") << unionFilter.properties() << QString() << "( (testProperty=12345) OR (testProperty=1) OR (testProperty='a string') )";
219+ filterValues.clear();
220+ filterValues[":filterValue0"] = 12345;
221+ filterValues[":filterValue1"] = true;
222+ filterValues[":filterValue2"] = "a string";
223+ QTest::newRow("union filter") << unionFilter.properties() << filterValues << QString() << "( (testProperty=:filterValue0) OR (testProperty=:filterValue1) OR (testProperty=:filterValue2) )";
224 }
225
226 void SqlitePluginTest::testFilterToString()
227 {
228 QFETCH(QVariantMap, filterProperties);
229+ QFETCH(QVariantMap, filterValues);
230 QFETCH(QString, propertyPrefix);
231 QFETCH(QString, resultString);
232
233- QString result = mPlugin->filterToString(History::Filter::fromProperties(filterProperties), propertyPrefix);
234+ QVariantMap resultValues;
235+ QString result = mPlugin->filterToString(History::Filter::fromProperties(filterProperties), resultValues, propertyPrefix);
236 QCOMPARE(result, resultString);
237+ QCOMPARE(resultValues, filterValues);
238+
239 }
240
241 void SqlitePluginTest::testEscapeFilterValue_data()
242
243=== modified file 'src/tests/EventViewTest.cpp'
244--- src/tests/EventViewTest.cpp 2013-12-09 21:18:14 +0000
245+++ src/tests/EventViewTest.cpp 2015-01-28 23:16:22 +0000
246@@ -39,6 +39,7 @@
247 private Q_SLOTS:
248 void initTestCase();
249 void testNextPage();
250+ void testFilter_data();
251 void testFilter();
252 void testSort();
253
254@@ -66,28 +67,66 @@
255 events = view->nextPage();
256 }
257
258- QCOMPARE(allEvents.count(), EVENT_COUNT * 2);
259+ QCOMPARE(allEvents.count(), EVENT_COUNT * 2 + 1); // include the group text event
260 Q_FOREACH(const History::Event &event, events) {
261 QCOMPARE(event.type(), History::EventTypeText);
262 }
263 }
264
265-void EventViewTest::testFilter()
266+void EventViewTest::testFilter_data()
267 {
268+ QTest::addColumn<QVariantMap>("filterProperties");
269+ QTest::addColumn<History::EventType>("eventType");
270+ QTest::addColumn<int>("resultCount");
271+ QTest::addColumn<QVariantMap>("firstEventProperties");
272+
273 History::IntersectionFilter filter;
274 filter.append(History::Filter(History::FieldAccountId, "account0"));
275 filter.append(History::Filter(History::FieldThreadId, "participant0"));
276 filter.append(History::Filter(History::FieldEventId, "event21"));
277-
278- History::EventViewPtr view = History::Manager::instance()->queryEvents(History::EventTypeVoice, History::Sort(History::FieldAccountId), filter);
279+ History::VoiceEvent voiceEvent = History::Manager::instance()->getSingleEvent(History::EventTypeVoice,
280+ "account0",
281+ "participant0",
282+ "event21");
283+ QVERIFY(!voiceEvent.isNull());
284+ QTest::newRow("filter by accountId, threadId and eventId") << filter.properties() << History::EventTypeVoice << 1 << voiceEvent.properties();
285+
286+ filter.clear();
287+ QStringList participants;
288+ participants << "groupParticipant1" << "groupParticipant2";
289+ History::Thread thread = History::Manager::instance()->threadForParticipants("groupAccount",
290+ History::EventTypeText,
291+ participants,
292+ History::MatchCaseSensitive);
293+ QVERIFY(!thread.isNull());
294+ History::TextEvent textEvent = History::Manager::instance()->getSingleEvent(History::EventTypeText,
295+ thread.accountId(),
296+ thread.threadId(),
297+ "groupEvent0");
298+ QVERIFY(!textEvent.isNull());
299+ filter.append(History::Filter(History::FieldAccountId, thread.accountId()));
300+ filter.append(History::Filter(History::FieldThreadId, thread.threadId()));
301+ QTest::newRow("filter for a group conversation") << filter.properties() << History::EventTypeText << 1 << textEvent.properties();
302+}
303+
304+void EventViewTest::testFilter()
305+{
306+ QFETCH(QVariantMap, filterProperties);
307+ QFETCH(History::EventType, eventType);
308+ QFETCH(int, resultCount);
309+ QFETCH(QVariantMap, firstEventProperties);
310+
311+ History::Filter filter = History::Filter::fromProperties(filterProperties);
312+ History::EventViewPtr view = History::Manager::instance()->queryEvents(eventType,
313+ History::Sort(History::FieldAccountId),
314+ filter);
315 QVERIFY(view->isValid());
316+
317 History::Events events = view->nextPage();
318- QCOMPARE(events.count(), 1);
319+ QCOMPARE(events.count(), resultCount);
320+
321 History::Event event = events.first();
322- QCOMPARE(event.accountId(), QString("account0"));
323- QCOMPARE(event.type(), History::EventTypeVoice);
324- QCOMPARE(event.threadId(), QString("participant0"));
325- QCOMPARE(event.eventId(), QString("event21"));
326+ QCOMPARE(event.properties(), firstEventProperties);
327
328 // make sure no more items are returned
329 QVERIFY(view->nextPage().isEmpty());
330@@ -106,7 +145,7 @@
331 }
332
333 QCOMPARE(allEvents.first().eventId(), QString("event00"));
334- QCOMPARE(allEvents.last().eventId(), QString("event%1").arg(EVENT_COUNT-1));
335+ QCOMPARE(allEvents.last().eventId(), QString("groupEvent0"));
336
337 History::Sort descendingSort(History::FieldEventId, Qt::DescendingOrder);
338 allEvents.clear();
339@@ -118,7 +157,7 @@
340 events = view->nextPage();
341 }
342
343- QCOMPARE(allEvents.first().eventId(), QString("event%1").arg(EVENT_COUNT-1));
344+ QCOMPARE(allEvents.first().eventId(), QString("groupEvent0"));
345 QCOMPARE(allEvents.last().eventId(), QString("event00"));
346 }
347
348@@ -166,6 +205,49 @@
349 }
350 QVERIFY(History::Manager::instance()->writeEvents(events));
351 }
352+
353+ // create a text thread with multiple participants
354+ QStringList participants;
355+ participants << "groupParticipant1" << "groupParticipant2";
356+
357+ History::Thread groupTextThread = History::Manager::instance()->threadForParticipants("groupAccount",
358+ History::EventTypeText,
359+ participants,
360+ History::MatchCaseSensitive,
361+ true);
362+ QVERIFY(!groupTextThread.isNull());
363+
364+ // and write a single event to it, just to make sure it works
365+ History::TextEvent groupTextEvent("groupAccount",
366+ groupTextThread.threadId(),
367+ "groupEvent0",
368+ "groupSender",
369+ QDateTime::currentDateTime(),
370+ true,
371+ "A group message",
372+ History::MessageTypeText);
373+ QVERIFY(History::Manager::instance()->writeEvents(History::Events() << groupTextEvent));
374+
375+ // create a text thread with multiple participants
376+ participants.clear();
377+ participants << "groupParticipant1" << "groupParticipant2";
378+
379+ History::Thread groupVoiceThread = History::Manager::instance()->threadForParticipants("groupAccount",
380+ History::EventTypeVoice,
381+ participants,
382+ History::MatchCaseSensitive,
383+ true);
384+ QVERIFY(!groupVoiceThread.isNull());
385+
386+ // and write a single event to it, just to make sure it works
387+ History::VoiceEvent groupVoiceEvent("groupAccount",
388+ groupVoiceThread.threadId(),
389+ "groupEvent0",
390+ "groupSender",
391+ QDateTime::currentDateTime(),
392+ true,
393+ true);
394+ QVERIFY(History::Manager::instance()->writeEvents(History::Events() << groupVoiceEvent));
395 }
396
397 QTEST_MAIN(EventViewTest)

Subscribers

People subscribed via source and target branches