Merge lp:~kalikiana/u1db-qt/wonderiousFields into lp:u1db-qt

Proposed by Cris Dywan
Status: Merged
Approved by: Cris Dywan
Approved revision: 119
Merged at revision: 114
Proposed branch: lp:~kalikiana/u1db-qt/wonderiousFields
Merge into: lp:u1db-qt
Diff against target: 525 lines (+219/-135)
3 files modified
src/query.cpp (+66/-114)
src/query.h (+3/-1)
tests/tst_query.qml (+150/-20)
To merge this branch: bzr merge lp:~kalikiana/u1db-qt/wonderiousFields
Reviewer Review Type Date Requested Status
Benjamin Zeller Approve
PS Jenkins bot continuous-integration Approve
Review via email: mp+207968@code.launchpad.net

Commit message

Reverse query logic to check non-matching and internally convert between query syntaxes

Description of the change

- bug 1215898 example works
- bug 1214538 was already fixed before it seems
- bug 1284194 works

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Nekhelesh Ramananthan (nik90) wrote :

On testing, I found the following results,
1. bug #1214538 is fixed
2. bug #1284194 doesn't work with 2 Queries. With 1 Query, the filtering of elements with "show" works as expected.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Benjamin Zeller (zeller-benjamin) wrote :

3 Mentioned examples work, ready to merge

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/query.cpp'
2--- src/query.cpp 2014-02-19 09:39:07 +0000
3+++ src/query.cpp 2014-03-07 17:54:50 +0000
4@@ -122,6 +122,34 @@
5 void Query::generateQueryResults()
6 {
7 QList<QVariantMap> results(m_index->getAllResults());
8+
9+ /* Convert "*" or 123 or "aa" into a list */
10+ /* Also convert ["aa", 123] into [{foo:"aa", bar:123}] */
11+ QVariantList queryList(m_query.toList());
12+ if (queryList.empty()) {
13+ // * is the default if query is empty
14+ if (!m_query.isValid())
15+ queryList.append(QVariant(QString("*")));
16+ else
17+ queryList.append(m_query);
18+ }
19+ if (queryList.at(0).type() != QVariant::Map) {
20+ QVariantList oldQueryList(queryList);
21+ QListIterator<QVariant> j(oldQueryList);
22+ QListIterator<QString> k(m_index->getExpression());
23+ while(j.hasNext() && k.hasNext()) {
24+ QVariant j_value = j.next();
25+ QString k_value = k.next();
26+ QVariantMap valueMap;
27+ // Strip hierarchical components
28+ if (k_value.contains("."))
29+ valueMap.insert(k_value.split(".").last(), j_value);
30+ else
31+ valueMap.insert(k_value, j_value);
32+ queryList.append(QVariant(valueMap));
33+ }
34+ }
35+
36 Q_FOREACH (QVariantMap mapIdResult, results) {
37 QString docId((mapIdResult["docId"]).toString());
38 QVariant result_variant(mapIdResult["result"]);
39@@ -135,10 +163,9 @@
40
41 j.next();
42
43- bool tmp_match = queryField(j.key(), j.value());
44-
45- if(tmp_match == false){
46+ if (!iterateQueryList(queryList, j.key(), j.value())) {
47 match = false;
48+ break;
49 }
50
51 }
52@@ -158,7 +185,6 @@
53
54 Q_EMIT documentsChanged(m_documents);
55 Q_EMIT resultsChanged(m_results);
56-
57 }
58
59 /*!
60@@ -175,87 +201,41 @@
61
62 /*!
63 \internal
64- Query a single field.
65- */
66-bool Query::queryField(QString field, QVariant value){
67-
68- bool match = false;
69-
70- QVariant query = getQuery();
71- // * is the default if query is empty
72- if (!query.isValid())
73- query = QVariant(QString("*"));
74- QString typeName = query.typeName();
75-
76- if(typeName == "QString")
77- {
78- QString query_string = query.toString();
79- match = queryString(query_string, value);
80- }
81- else if(typeName == "int")
82- {
83- QString query_string = query.toString();
84- match = queryString(query_string, value);
85- }
86- else if(typeName == "QVariantList")
87- {
88- match = iterateQueryList(query, field, value);
89- }
90- else
91- {
92- m_query = "";
93- qWarning("u1db: Unexpected type %s for query", qPrintable(typeName));
94- }
95-
96- return match;
97-
98-}
99-
100-/*!
101- \internal
102 Loop through the query assuming it's a list.
103+
104+ For example:
105+ queryList: { type: 'show' }
106+ field: 'type'
107+ value: 'show'
108 */
109-bool Query::iterateQueryList(QVariant query, QString field, QVariant value)
110+bool Query::iterateQueryList(QVariantList queryList, QString field, QVariant value)
111 {
112-
113- bool match = false;
114-
115- QList<QVariant> query_list = query.toList();
116- QListIterator<QVariant> j(query_list);
117+ QListIterator<QVariant> j(queryList);
118
119 while (j.hasNext()) {
120-
121 QVariant j_value = j.next();
122-
123- QString typeName = j_value.typeName();
124-
125- if(typeName == "QVariantMap")
126- {
127- match = queryMap(j_value.toMap(), value.toString(), field);
128-
129- if(match == true){
130- break;
131- }
132-
133- }
134- else if(typeName == "QString"){
135-
136- match = queryString(j_value.toString(), value);
137-
138- if(match == true){
139- break;
140- }
141-
142- }
143- else
144- {
145- m_query = "";
146- qWarning("u1db: Unexpected type %s for query", qPrintable(typeName));
147- }
148-
149+ QVariantMap valueMap(j_value.toMap());
150+ if (!queryMap(valueMap, value.toString(), field))
151+ return false;
152 }
153
154- return match;
155+ return true;
156+}
157+
158+/*!
159+ \internal
160+ Verify that query is an identical or wild card match.
161+ */
162+bool Query::queryMatchesValue(QString query, QString value)
163+{
164+ if (query == "*")
165+ return true;
166+ if (query == value)
167+ return true;
168+ if (!query.contains ("*"))
169+ return false;
170+ QString prefix(query.split("*")[0]);
171+ return value.startsWith(prefix, Qt::CaseSensitive);
172 }
173
174 /*!
175@@ -274,36 +254,20 @@
176 return false;
177 }
178
179- bool match = false;
180-
181- if(query == "*"){
182- return true;
183- }
184- else if(query == value){
185- return true;
186- }
187- else if(query.contains("*")){
188- QStringList k_string_list = query.split("*");
189- QString k_string = k_string_list[0];
190- match = value.toString().startsWith(k_string,Qt::CaseSensitive);
191-
192- return match;
193-
194- }
195-
196-
197- return match;
198+ return queryMatchesValue(query, value.toString());
199 }
200
201 /*!
202 \internal
203 Loop through the given map of keys and queries.
204+
205+ For example:
206+ map: { type: 'show' }
207+ value: { 'show' }
208+ field: 'type'
209 */
210 bool Query::queryMap(QVariantMap map, QString value, QString field)
211 {
212-
213- bool match = false;
214-
215 QMapIterator<QString,QVariant> k(map);
216
217 while(k.hasNext()){
218@@ -314,24 +278,12 @@
219 QString query = k_variant.toString();
220
221 if(field == k_key){
222-
223- if(query == "*"){
224- return true;
225- }
226- else if(query == value){
227- return true;
228- }
229- else if(query.contains("*")){
230- QStringList k_string_list = query.split("*");
231- QString k_string = k_string_list[0];
232- match = value.startsWith(k_string,Qt::CaseSensitive);
233- return match;
234- }
235-
236+ if (!queryMatchesValue(query, value))
237+ return false;
238 }
239 }
240
241- return match;
242+ return true;
243 }
244
245 /*!
246
247=== modified file 'src/query.h'
248--- src/query.h 2014-01-31 19:20:07 +0000
249+++ src/query.h 2014-03-07 17:54:50 +0000
250@@ -68,8 +68,10 @@
251
252 void onDataInvalidated();
253
254+ bool debug();
255 void generateQueryResults();
256- bool iterateQueryList(QVariant query, QString field, QVariant value);
257+ bool iterateQueryList(QVariantList list, QString field, QVariant value);
258+ bool queryMatchesValue(QString query, QString value);
259 bool queryString(QString query, QVariant value);
260 bool queryMap(QVariantMap map, QString value, QString field);
261 bool queryField(QString field, QVariant value);
262
263=== modified file 'tests/tst_query.qml'
264--- tests/tst_query.qml 2014-02-19 09:39:07 +0000
265+++ tests/tst_query.qml 2014-03-07 17:54:50 +0000
266@@ -26,7 +26,6 @@
267
268 U1db.Database {
269 id: gents
270- path: 'aDatabaseU'
271 }
272
273 U1db.Document {
274@@ -47,6 +46,18 @@
275 contents: { 'misc': { 'software': 'linux', 'sports': [ 'basketball', 'hockey' ] }, 'date': '2014-01-01' , 'gents': [ { 'name': 'Ivanka', 'phone': 00321 }, ] }
276 }
277
278+ U1db.Document {
279+ database: gents
280+ docId: 'F'
281+ contents: { 'details': { 'name': 'spy', 'type': 'hide', 'colour': 'blue' } }
282+ }
283+
284+ U1db.Document {
285+ database: gents
286+ docId: 'G'
287+ contents: { 'details': { 'name': 'kid', 'type': 'show', 'colour': 'green' } }
288+ }
289+
290 U1db.Index {
291 id: byPhone
292 database: gents
293@@ -100,6 +111,12 @@
294 U1db.Query {
295 id: i12345Phone
296 index: byPhone
297+ query: [ { 'phone': 12345 } ]
298+ }
299+
300+ U1db.Query {
301+ id: i12345PhoneSimple
302+ index: byPhone
303 query: 12345
304 }
305
306@@ -112,6 +129,12 @@
307 U1db.Query {
308 id: ivankaAllNamePhone
309 index: byNamePhone
310+ query: [ { name: 'Ivanka', phone: '*' } ]
311+ }
312+
313+ U1db.Query {
314+ id: ivankaAllNamePhoneSimple
315+ index: byNamePhone
316 query: ['Ivanka', '*']
317 }
318
319@@ -122,17 +145,96 @@
320 }
321
322 U1db.Query {
323- id: wrongQuery
324- index: byNamePhone
325- query: [{ 'name': 'Ivanka', 'phone': '*' }]
326- }
327-
328- U1db.Query {
329 id: toplevelQuery
330 index: byDate
331 query: [{ 'date': '2014*', 'sports': 'basketball', 'software': 'linux' }]
332 }
333
334+ U1db.Query {
335+ id: toplevelQuerySimple
336+ index: byDate
337+ query: [ '2014*', 'basketball', 'linux' ]
338+ }
339+
340+ U1db.Query {
341+ id: queryOne
342+ index: U1db.Index {
343+ database: gents
344+ name: 'one'
345+ expression: [ 'details.type' ]
346+ }
347+ query: [ 'show' ]
348+ }
349+
350+ U1db.Query {
351+ id: queryBothSimple
352+ index: U1db.Index {
353+ database: gents
354+ name: 'bothSimple'
355+ expression: [ 'details.type', 'details.colour' ]
356+ }
357+ query: [ 'show', '*' ]
358+ }
359+
360+ U1db.Query {
361+ id: queryBoth
362+ index: U1db.Index {
363+ database: gents
364+ name: 'both'
365+ expression: [ 'details.type', 'details.colour' ]
366+ }
367+ query: [ { type: 'show', colour: '*' } ]
368+ }
369+
370+ U1db.Database {
371+ id: tokusatsu
372+ }
373+
374+ U1db.Document {
375+ database: tokusatsu
376+ docId: 'ooo'
377+ contents: { 'series': 'ooo', 'type': 'rider' }
378+ }
379+
380+ U1db.Document {
381+ database: tokusatsu
382+ docId: 'gokaiger'
383+ contents: { 'series': 'gokaiger', 'type': 'sentai' }
384+ }
385+
386+ U1db.Document {
387+ id: tokusatsuDocumentWizard
388+ docId: 'wizard'
389+ contents: { 'series': 'wizard', 'type': 'rider',
390+ 'transformations': ['Flame Style','Water Style'] }
391+ }
392+
393+ U1db.Document {
394+ id: tokusatsuDocumentDino
395+ docId: 'dino'
396+ contents: { 'series': 'zyuranger', 'scarf': false, 'type': 'sentai',
397+ 'beasts': ['T-Rex', 'Mastodon'] }
398+ }
399+
400+ U1db.Index {
401+ id: bySeries
402+ database: tokusatsu
403+ name: 'by-series'
404+ expression: ['series', 'type']
405+ }
406+
407+ U1db.Query {
408+ id: allHeroesWithType
409+ index: bySeries
410+ query: [{ 'series': '*' }, { 'type': '*' }]
411+ }
412+
413+ U1db.Query {
414+ id: allHeroesSeriesOnly
415+ index: bySeries
416+ query: [{ 'series': '*' }]
417+ }
418+
419 SignalSpy {
420 id: spyDocumentsChanged
421 target: defaultPhone
422@@ -154,7 +256,11 @@
423 return A
424 }
425
426- function compare (a, b) {
427+ function compareFail(a, b, msg) {
428+ compare(a, b, msg, true)
429+ }
430+
431+ function compare(a, b, msg, willFail) {
432 /* Override built-in compare to:
433 Match different JSON for identical values (number hash versus list)
434 Produce readable output for all JSON values
435@@ -163,8 +269,14 @@
436 return
437 var A = prettyJson(a), B = prettyJson(b)
438 if (A != B) {
439- fail('%1 != %2 (%3 != %4)'.arg(A).arg(B).arg(JSON.stringify(a)).arg(JSON.stringify(b)))
440+ if (willFail) {
441+ console.log('Expected failure: %1%2 != %3'.arg(msg ? msg + ': ' : '').arg(A).arg(B))
442+ return
443+ }
444+ fail('%5%1 != %2 (%3 != %4)'.arg(A).arg(B).arg(JSON.stringify(a)).arg(JSON.stringify(b)).arg(msg ? msg + ': ' : ''))
445 }
446+ if (willFail)
447+ fail('Expected to fail, but passed: %5%1 != %2 (%3 != %4)'.arg(A).arg(B).arg(JSON.stringify(a)).arg(JSON.stringify(b)).arg(msg ? msg + ': ' : ''))
448 }
449
450 function workaroundQueryAndWait (buggyQuery) {
451@@ -173,21 +285,12 @@
452 spyDocumentsChanged.wait();
453 }
454
455- function test_0_wrongUse () {
456- workaroundQueryAndWait(wrongQuery)
457- ignoreWarning('u1db: Unexpected type QVariantMap for query')
458- wrongQuery.query = { 'name': 'Ivanka' }
459- ignoreWarning('u1db: Unexpected type QObject* for query')
460- wrongQuery.query = defaultPhone
461- }
462-
463 function test_1_defaults () {
464 // We should get all documents
465 workaroundQueryAndWait(defaultPhone)
466 compare(defaultPhone.documents, ['1', '_', 'a'], 'uno')
467 compare(defaultPhone.results.length, 3, 'dos')
468 compare(defaultPhone.results.length, defaultPhone.documents.length, 'puntos')
469- // FIXME: compare(defaultPhone.results, [], 'dos')
470 // These queries are functionally equivalent
471 compare(defaultPhone.documents, allPhone.documents, 'tres')
472 compare(defaultPhone.documents, allPhoneList.documents, 'quatro')
473@@ -205,18 +308,21 @@
474 compare(s12345Phone.documents, ['1'], 'uno')
475 // It's okay to mix strings and numerical values
476 compare(s12345Phone.documents, i12345Phone.documents, 'dos')
477+ compare(i12345PhoneSimple.documents, i12345Phone.documents, 'tres')
478 }
479
480 function test_3_wildcards () {
481 // Trailing string wildcard
482 compare(s1wildcardPhone.documents, ['1'], 'uno')
483 // Last given field can use wildcards
484- // FIXME: compare(ivankaAllNamePhone.documents, ['_', 'a'], 'dos')
485+ compare(ivankaAllNamePhoneSimple.documents, ['_', 'a'], 'dos')
486+ compare(ivankaAllNamePhone.documents, ['_', 'a'], 'tres')
487 // These queries are functionally equivalent
488 workaroundQueryAndWait(ivankaAllNamePhoneKeywords)
489 workaroundQueryAndWait(ivankaAllNamePhone)
490- // FIXME: compare(ivankaAllNamePhone.documents, ivankaAllNamePhoneKeywords.documents, 'tres')
491+ compare(ivankaAllNamePhone.documents, ivankaAllNamePhoneKeywords.documents, 'tres')
492 compare(toplevelQuery.documents, ['_'])
493+ compare(toplevelQuerySimple.documents, ['_'], 'cinco')
494 }
495
496 function test_4_delete () {
497@@ -226,5 +332,29 @@
498 compare(defaultPhone.documents, ['1', 'a'], 'dos')
499 }
500
501+ function test_5_fields () {
502+ compare(queryOne.documents, ['G'], 'one field')
503+ compare(queryBoth.documents, ['G'], 'two fields')
504+ compare(queryBothSimple.documents, ['G'], 'two fields simple')
505+ }
506+
507+ function test_6_definition () {
508+ workaroundQueryAndWait(allHeroesWithType)
509+ compare(allHeroesWithType.documents, ['gokaiger', 'ooo'], 'ichi')
510+ workaroundQueryAndWait(allHeroesSeriesOnly)
511+ compare(allHeroesSeriesOnly.documents, ['gokaiger', 'ooo'], 'ni')
512+ compare(allHeroesWithType.documents, allHeroesSeriesOnly.documents, 'doube-check')
513+ // Add a document with extra fields
514+ tokusatsu.putDoc(tokusatsuDocumentWizard.contents, tokusatsuDocumentWizard.docId)
515+ workaroundQueryAndWait(allHeroesWithType)
516+ compare(allHeroesWithType.documents, ['gokaiger', 'ooo', 'wizard'], 'san')
517+ workaroundQueryAndWait(allHeroesSeriesOnly)
518+ compare(allHeroesWithType.documents, allHeroesSeriesOnly.documents, 'chi')
519+ // Add a document with mixed custom fields
520+ tokusatsu.putDoc(tokusatsuDocumentDino.contents, tokusatsuDocumentDino.docId)
521+ workaroundQueryAndWait(allHeroesWithType)
522+ compare(allHeroesWithType.documents, ['dino', 'gokaiger', 'ooo', 'wizard'], 'go')
523+ compare(allHeroesWithType.documents, allHeroesSeriesOnly.documents, 'roku')
524+ }
525 } }
526

Subscribers

People subscribed via source and target branches

to all changes: