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
=== modified file 'src/query.cpp'
--- src/query.cpp 2014-02-19 09:39:07 +0000
+++ src/query.cpp 2014-03-07 17:54:50 +0000
@@ -122,6 +122,34 @@
122void Query::generateQueryResults()122void Query::generateQueryResults()
123{123{
124 QList<QVariantMap> results(m_index->getAllResults());124 QList<QVariantMap> results(m_index->getAllResults());
125
126 /* Convert "*" or 123 or "aa" into a list */
127 /* Also convert ["aa", 123] into [{foo:"aa", bar:123}] */
128 QVariantList queryList(m_query.toList());
129 if (queryList.empty()) {
130 // * is the default if query is empty
131 if (!m_query.isValid())
132 queryList.append(QVariant(QString("*")));
133 else
134 queryList.append(m_query);
135 }
136 if (queryList.at(0).type() != QVariant::Map) {
137 QVariantList oldQueryList(queryList);
138 QListIterator<QVariant> j(oldQueryList);
139 QListIterator<QString> k(m_index->getExpression());
140 while(j.hasNext() && k.hasNext()) {
141 QVariant j_value = j.next();
142 QString k_value = k.next();
143 QVariantMap valueMap;
144 // Strip hierarchical components
145 if (k_value.contains("."))
146 valueMap.insert(k_value.split(".").last(), j_value);
147 else
148 valueMap.insert(k_value, j_value);
149 queryList.append(QVariant(valueMap));
150 }
151 }
152
125 Q_FOREACH (QVariantMap mapIdResult, results) {153 Q_FOREACH (QVariantMap mapIdResult, results) {
126 QString docId((mapIdResult["docId"]).toString());154 QString docId((mapIdResult["docId"]).toString());
127 QVariant result_variant(mapIdResult["result"]);155 QVariant result_variant(mapIdResult["result"]);
@@ -135,10 +163,9 @@
135163
136 j.next();164 j.next();
137165
138 bool tmp_match = queryField(j.key(), j.value());166 if (!iterateQueryList(queryList, j.key(), j.value())) {
139
140 if(tmp_match == false){
141 match = false;167 match = false;
168 break;
142 }169 }
143170
144 }171 }
@@ -158,7 +185,6 @@
158185
159 Q_EMIT documentsChanged(m_documents);186 Q_EMIT documentsChanged(m_documents);
160 Q_EMIT resultsChanged(m_results);187 Q_EMIT resultsChanged(m_results);
161
162}188}
163189
164/*!190/*!
@@ -175,87 +201,41 @@
175201
176/*!202/*!
177 \internal203 \internal
178 Query a single field.
179 */
180bool Query::queryField(QString field, QVariant value){
181
182 bool match = false;
183
184 QVariant query = getQuery();
185 // * is the default if query is empty
186 if (!query.isValid())
187 query = QVariant(QString("*"));
188 QString typeName = query.typeName();
189
190 if(typeName == "QString")
191 {
192 QString query_string = query.toString();
193 match = queryString(query_string, value);
194 }
195 else if(typeName == "int")
196 {
197 QString query_string = query.toString();
198 match = queryString(query_string, value);
199 }
200 else if(typeName == "QVariantList")
201 {
202 match = iterateQueryList(query, field, value);
203 }
204 else
205 {
206 m_query = "";
207 qWarning("u1db: Unexpected type %s for query", qPrintable(typeName));
208 }
209
210 return match;
211
212}
213
214/*!
215 \internal
216 Loop through the query assuming it's a list.204 Loop through the query assuming it's a list.
205
206 For example:
207 queryList: { type: 'show' }
208 field: 'type'
209 value: 'show'
217 */210 */
218bool Query::iterateQueryList(QVariant query, QString field, QVariant value)211bool Query::iterateQueryList(QVariantList queryList, QString field, QVariant value)
219{212{
220213 QListIterator<QVariant> j(queryList);
221 bool match = false;
222
223 QList<QVariant> query_list = query.toList();
224 QListIterator<QVariant> j(query_list);
225214
226 while (j.hasNext()) {215 while (j.hasNext()) {
227
228 QVariant j_value = j.next();216 QVariant j_value = j.next();
229217 QVariantMap valueMap(j_value.toMap());
230 QString typeName = j_value.typeName();218 if (!queryMap(valueMap, value.toString(), field))
231219 return false;
232 if(typeName == "QVariantMap")
233 {
234 match = queryMap(j_value.toMap(), value.toString(), field);
235
236 if(match == true){
237 break;
238 }
239
240 }
241 else if(typeName == "QString"){
242
243 match = queryString(j_value.toString(), value);
244
245 if(match == true){
246 break;
247 }
248
249 }
250 else
251 {
252 m_query = "";
253 qWarning("u1db: Unexpected type %s for query", qPrintable(typeName));
254 }
255
256 }220 }
257221
258 return match;222 return true;
223}
224
225/*!
226 \internal
227 Verify that query is an identical or wild card match.
228 */
229bool Query::queryMatchesValue(QString query, QString value)
230{
231 if (query == "*")
232 return true;
233 if (query == value)
234 return true;
235 if (!query.contains ("*"))
236 return false;
237 QString prefix(query.split("*")[0]);
238 return value.startsWith(prefix, Qt::CaseSensitive);
259}239}
260240
261/*!241/*!
@@ -274,36 +254,20 @@
274 return false;254 return false;
275 }255 }
276256
277 bool match = false;257 return queryMatchesValue(query, value.toString());
278
279 if(query == "*"){
280 return true;
281 }
282 else if(query == value){
283 return true;
284 }
285 else if(query.contains("*")){
286 QStringList k_string_list = query.split("*");
287 QString k_string = k_string_list[0];
288 match = value.toString().startsWith(k_string,Qt::CaseSensitive);
289
290 return match;
291
292 }
293
294
295 return match;
296}258}
297259
298/*!260/*!
299 \internal261 \internal
300 Loop through the given map of keys and queries.262 Loop through the given map of keys and queries.
263
264 For example:
265 map: { type: 'show' }
266 value: { 'show' }
267 field: 'type'
301 */268 */
302bool Query::queryMap(QVariantMap map, QString value, QString field)269bool Query::queryMap(QVariantMap map, QString value, QString field)
303{270{
304
305 bool match = false;
306
307 QMapIterator<QString,QVariant> k(map);271 QMapIterator<QString,QVariant> k(map);
308272
309 while(k.hasNext()){273 while(k.hasNext()){
@@ -314,24 +278,12 @@
314 QString query = k_variant.toString();278 QString query = k_variant.toString();
315279
316 if(field == k_key){280 if(field == k_key){
317281 if (!queryMatchesValue(query, value))
318 if(query == "*"){282 return false;
319 return true;
320 }
321 else if(query == value){
322 return true;
323 }
324 else if(query.contains("*")){
325 QStringList k_string_list = query.split("*");
326 QString k_string = k_string_list[0];
327 match = value.startsWith(k_string,Qt::CaseSensitive);
328 return match;
329 }
330
331 }283 }
332 }284 }
333285
334 return match;286 return true;
335}287}
336288
337/*!289/*!
338290
=== modified file 'src/query.h'
--- src/query.h 2014-01-31 19:20:07 +0000
+++ src/query.h 2014-03-07 17:54:50 +0000
@@ -68,8 +68,10 @@
6868
69 void onDataInvalidated();69 void onDataInvalidated();
7070
71 bool debug();
71 void generateQueryResults();72 void generateQueryResults();
72 bool iterateQueryList(QVariant query, QString field, QVariant value);73 bool iterateQueryList(QVariantList list, QString field, QVariant value);
74 bool queryMatchesValue(QString query, QString value);
73 bool queryString(QString query, QVariant value);75 bool queryString(QString query, QVariant value);
74 bool queryMap(QVariantMap map, QString value, QString field);76 bool queryMap(QVariantMap map, QString value, QString field);
75 bool queryField(QString field, QVariant value);77 bool queryField(QString field, QVariant value);
7678
=== modified file 'tests/tst_query.qml'
--- tests/tst_query.qml 2014-02-19 09:39:07 +0000
+++ tests/tst_query.qml 2014-03-07 17:54:50 +0000
@@ -26,7 +26,6 @@
2626
27 U1db.Database {27 U1db.Database {
28 id: gents28 id: gents
29 path: 'aDatabaseU'
30 }29 }
3130
32 U1db.Document {31 U1db.Document {
@@ -47,6 +46,18 @@
47 contents: { 'misc': { 'software': 'linux', 'sports': [ 'basketball', 'hockey' ] }, 'date': '2014-01-01' , 'gents': [ { 'name': 'Ivanka', 'phone': 00321 }, ] }46 contents: { 'misc': { 'software': 'linux', 'sports': [ 'basketball', 'hockey' ] }, 'date': '2014-01-01' , 'gents': [ { 'name': 'Ivanka', 'phone': 00321 }, ] }
48 }47 }
4948
49 U1db.Document {
50 database: gents
51 docId: 'F'
52 contents: { 'details': { 'name': 'spy', 'type': 'hide', 'colour': 'blue' } }
53 }
54
55 U1db.Document {
56 database: gents
57 docId: 'G'
58 contents: { 'details': { 'name': 'kid', 'type': 'show', 'colour': 'green' } }
59 }
60
50 U1db.Index {61 U1db.Index {
51 id: byPhone62 id: byPhone
52 database: gents63 database: gents
@@ -100,6 +111,12 @@
100 U1db.Query {111 U1db.Query {
101 id: i12345Phone112 id: i12345Phone
102 index: byPhone113 index: byPhone
114 query: [ { 'phone': 12345 } ]
115 }
116
117 U1db.Query {
118 id: i12345PhoneSimple
119 index: byPhone
103 query: 12345120 query: 12345
104 }121 }
105122
@@ -112,6 +129,12 @@
112 U1db.Query {129 U1db.Query {
113 id: ivankaAllNamePhone130 id: ivankaAllNamePhone
114 index: byNamePhone131 index: byNamePhone
132 query: [ { name: 'Ivanka', phone: '*' } ]
133 }
134
135 U1db.Query {
136 id: ivankaAllNamePhoneSimple
137 index: byNamePhone
115 query: ['Ivanka', '*']138 query: ['Ivanka', '*']
116 }139 }
117140
@@ -122,17 +145,96 @@
122 }145 }
123146
124 U1db.Query {147 U1db.Query {
125 id: wrongQuery
126 index: byNamePhone
127 query: [{ 'name': 'Ivanka', 'phone': '*' }]
128 }
129
130 U1db.Query {
131 id: toplevelQuery148 id: toplevelQuery
132 index: byDate149 index: byDate
133 query: [{ 'date': '2014*', 'sports': 'basketball', 'software': 'linux' }]150 query: [{ 'date': '2014*', 'sports': 'basketball', 'software': 'linux' }]
134 }151 }
135152
153 U1db.Query {
154 id: toplevelQuerySimple
155 index: byDate
156 query: [ '2014*', 'basketball', 'linux' ]
157 }
158
159 U1db.Query {
160 id: queryOne
161 index: U1db.Index {
162 database: gents
163 name: 'one'
164 expression: [ 'details.type' ]
165 }
166 query: [ 'show' ]
167 }
168
169 U1db.Query {
170 id: queryBothSimple
171 index: U1db.Index {
172 database: gents
173 name: 'bothSimple'
174 expression: [ 'details.type', 'details.colour' ]
175 }
176 query: [ 'show', '*' ]
177 }
178
179 U1db.Query {
180 id: queryBoth
181 index: U1db.Index {
182 database: gents
183 name: 'both'
184 expression: [ 'details.type', 'details.colour' ]
185 }
186 query: [ { type: 'show', colour: '*' } ]
187 }
188
189 U1db.Database {
190 id: tokusatsu
191 }
192
193 U1db.Document {
194 database: tokusatsu
195 docId: 'ooo'
196 contents: { 'series': 'ooo', 'type': 'rider' }
197 }
198
199 U1db.Document {
200 database: tokusatsu
201 docId: 'gokaiger'
202 contents: { 'series': 'gokaiger', 'type': 'sentai' }
203 }
204
205 U1db.Document {
206 id: tokusatsuDocumentWizard
207 docId: 'wizard'
208 contents: { 'series': 'wizard', 'type': 'rider',
209 'transformations': ['Flame Style','Water Style'] }
210 }
211
212 U1db.Document {
213 id: tokusatsuDocumentDino
214 docId: 'dino'
215 contents: { 'series': 'zyuranger', 'scarf': false, 'type': 'sentai',
216 'beasts': ['T-Rex', 'Mastodon'] }
217 }
218
219 U1db.Index {
220 id: bySeries
221 database: tokusatsu
222 name: 'by-series'
223 expression: ['series', 'type']
224 }
225
226 U1db.Query {
227 id: allHeroesWithType
228 index: bySeries
229 query: [{ 'series': '*' }, { 'type': '*' }]
230 }
231
232 U1db.Query {
233 id: allHeroesSeriesOnly
234 index: bySeries
235 query: [{ 'series': '*' }]
236 }
237
136 SignalSpy {238 SignalSpy {
137 id: spyDocumentsChanged239 id: spyDocumentsChanged
138 target: defaultPhone240 target: defaultPhone
@@ -154,7 +256,11 @@
154 return A256 return A
155 }257 }
156258
157 function compare (a, b) {259 function compareFail(a, b, msg) {
260 compare(a, b, msg, true)
261 }
262
263 function compare(a, b, msg, willFail) {
158 /* Override built-in compare to:264 /* Override built-in compare to:
159 Match different JSON for identical values (number hash versus list)265 Match different JSON for identical values (number hash versus list)
160 Produce readable output for all JSON values266 Produce readable output for all JSON values
@@ -163,8 +269,14 @@
163 return269 return
164 var A = prettyJson(a), B = prettyJson(b)270 var A = prettyJson(a), B = prettyJson(b)
165 if (A != B) {271 if (A != B) {
166 fail('%1 != %2 (%3 != %4)'.arg(A).arg(B).arg(JSON.stringify(a)).arg(JSON.stringify(b)))272 if (willFail) {
273 console.log('Expected failure: %1%2 != %3'.arg(msg ? msg + ': ' : '').arg(A).arg(B))
274 return
275 }
276 fail('%5%1 != %2 (%3 != %4)'.arg(A).arg(B).arg(JSON.stringify(a)).arg(JSON.stringify(b)).arg(msg ? msg + ': ' : ''))
167 }277 }
278 if (willFail)
279 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 + ': ' : ''))
168 }280 }
169281
170 function workaroundQueryAndWait (buggyQuery) {282 function workaroundQueryAndWait (buggyQuery) {
@@ -173,21 +285,12 @@
173 spyDocumentsChanged.wait();285 spyDocumentsChanged.wait();
174 }286 }
175287
176 function test_0_wrongUse () {
177 workaroundQueryAndWait(wrongQuery)
178 ignoreWarning('u1db: Unexpected type QVariantMap for query')
179 wrongQuery.query = { 'name': 'Ivanka' }
180 ignoreWarning('u1db: Unexpected type QObject* for query')
181 wrongQuery.query = defaultPhone
182 }
183
184 function test_1_defaults () {288 function test_1_defaults () {
185 // We should get all documents289 // We should get all documents
186 workaroundQueryAndWait(defaultPhone)290 workaroundQueryAndWait(defaultPhone)
187 compare(defaultPhone.documents, ['1', '_', 'a'], 'uno')291 compare(defaultPhone.documents, ['1', '_', 'a'], 'uno')
188 compare(defaultPhone.results.length, 3, 'dos')292 compare(defaultPhone.results.length, 3, 'dos')
189 compare(defaultPhone.results.length, defaultPhone.documents.length, 'puntos')293 compare(defaultPhone.results.length, defaultPhone.documents.length, 'puntos')
190 // FIXME: compare(defaultPhone.results, [], 'dos')
191 // These queries are functionally equivalent294 // These queries are functionally equivalent
192 compare(defaultPhone.documents, allPhone.documents, 'tres')295 compare(defaultPhone.documents, allPhone.documents, 'tres')
193 compare(defaultPhone.documents, allPhoneList.documents, 'quatro')296 compare(defaultPhone.documents, allPhoneList.documents, 'quatro')
@@ -205,18 +308,21 @@
205 compare(s12345Phone.documents, ['1'], 'uno')308 compare(s12345Phone.documents, ['1'], 'uno')
206 // It's okay to mix strings and numerical values309 // It's okay to mix strings and numerical values
207 compare(s12345Phone.documents, i12345Phone.documents, 'dos')310 compare(s12345Phone.documents, i12345Phone.documents, 'dos')
311 compare(i12345PhoneSimple.documents, i12345Phone.documents, 'tres')
208 }312 }
209313
210 function test_3_wildcards () {314 function test_3_wildcards () {
211 // Trailing string wildcard315 // Trailing string wildcard
212 compare(s1wildcardPhone.documents, ['1'], 'uno')316 compare(s1wildcardPhone.documents, ['1'], 'uno')
213 // Last given field can use wildcards317 // Last given field can use wildcards
214 // FIXME: compare(ivankaAllNamePhone.documents, ['_', 'a'], 'dos')318 compare(ivankaAllNamePhoneSimple.documents, ['_', 'a'], 'dos')
319 compare(ivankaAllNamePhone.documents, ['_', 'a'], 'tres')
215 // These queries are functionally equivalent320 // These queries are functionally equivalent
216 workaroundQueryAndWait(ivankaAllNamePhoneKeywords)321 workaroundQueryAndWait(ivankaAllNamePhoneKeywords)
217 workaroundQueryAndWait(ivankaAllNamePhone)322 workaroundQueryAndWait(ivankaAllNamePhone)
218 // FIXME: compare(ivankaAllNamePhone.documents, ivankaAllNamePhoneKeywords.documents, 'tres')323 compare(ivankaAllNamePhone.documents, ivankaAllNamePhoneKeywords.documents, 'tres')
219 compare(toplevelQuery.documents, ['_'])324 compare(toplevelQuery.documents, ['_'])
325 compare(toplevelQuerySimple.documents, ['_'], 'cinco')
220 }326 }
221327
222 function test_4_delete () {328 function test_4_delete () {
@@ -226,5 +332,29 @@
226 compare(defaultPhone.documents, ['1', 'a'], 'dos')332 compare(defaultPhone.documents, ['1', 'a'], 'dos')
227 }333 }
228334
335 function test_5_fields () {
336 compare(queryOne.documents, ['G'], 'one field')
337 compare(queryBoth.documents, ['G'], 'two fields')
338 compare(queryBothSimple.documents, ['G'], 'two fields simple')
339 }
340
341 function test_6_definition () {
342 workaroundQueryAndWait(allHeroesWithType)
343 compare(allHeroesWithType.documents, ['gokaiger', 'ooo'], 'ichi')
344 workaroundQueryAndWait(allHeroesSeriesOnly)
345 compare(allHeroesSeriesOnly.documents, ['gokaiger', 'ooo'], 'ni')
346 compare(allHeroesWithType.documents, allHeroesSeriesOnly.documents, 'doube-check')
347 // Add a document with extra fields
348 tokusatsu.putDoc(tokusatsuDocumentWizard.contents, tokusatsuDocumentWizard.docId)
349 workaroundQueryAndWait(allHeroesWithType)
350 compare(allHeroesWithType.documents, ['gokaiger', 'ooo', 'wizard'], 'san')
351 workaroundQueryAndWait(allHeroesSeriesOnly)
352 compare(allHeroesWithType.documents, allHeroesSeriesOnly.documents, 'chi')
353 // Add a document with mixed custom fields
354 tokusatsu.putDoc(tokusatsuDocumentDino.contents, tokusatsuDocumentDino.docId)
355 workaroundQueryAndWait(allHeroesWithType)
356 compare(allHeroesWithType.documents, ['dino', 'gokaiger', 'ooo', 'wizard'], 'go')
357 compare(allHeroesWithType.documents, allHeroesSeriesOnly.documents, 'roku')
358 }
229} }359} }
230360

Subscribers

People subscribed via source and target branches

to all changes: