uonedb-qt team mailing list archive
-
uonedb-qt team
-
Mailing list archive
-
Message #00296
[Merge] lp:~kalikiana/u1db-qt/wonderiousFields into lp:u1db-qt
Christian Dywan has proposed merging lp:~kalikiana/u1db-qt/wonderiousFields into lp:u1db-qt.
Commit message:
Reverse query logic to check non-matching and internally convert between query syntaxes
Requested reviews:
PS Jenkins bot (ps-jenkins): continuous-integration
U1DB Qt developers (uonedb-qt)
Related bugs:
Bug #1214538 in U1DB Qt/ QML: "Not indexing documents unless all fields are in the index expression clause"
https://bugs.launchpad.net/u1db-qt/+bug/1214538
Bug #1215898 in U1DB Qt/ QML: "Strange behaviour with multiple elements on first level in contents"
https://bugs.launchpad.net/u1db-qt/+bug/1215898
Bug #1284194 in U1DB Qt/ QML: "Wonderious fields constellation gets ignored"
https://bugs.launchpad.net/u1db-qt/+bug/1284194
For more details, see:
https://code.launchpad.net/~kalikiana/u1db-qt/wonderiousFields/+merge/207968
- bug 1215898 example works
- bug 1214538 was already fixed before it seems
- bug 1284194 works
--
https://code.launchpad.net/~kalikiana/u1db-qt/wonderiousFields/+merge/207968
Your team U1DB Qt developers is requested to review the proposed merge of lp:~kalikiana/u1db-qt/wonderiousFields into lp:u1db-qt.
=== 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 @@
void Query::generateQueryResults()
{
QList<QVariantMap> results(m_index->getAllResults());
+
+ /* Convert "*" or 123 or "aa" into a list */
+ /* Also convert ["aa", 123] into [{foo:"aa", bar:123}] */
+ QVariantList queryList(m_query.toList());
+ if (queryList.empty()) {
+ // * is the default if query is empty
+ if (!m_query.isValid())
+ queryList.append(QVariant(QString("*")));
+ else
+ queryList.append(m_query);
+ }
+ if (queryList.at(0).type() != QVariant::Map) {
+ QVariantList oldQueryList(queryList);
+ QListIterator<QVariant> j(oldQueryList);
+ QListIterator<QString> k(m_index->getExpression());
+ while(j.hasNext() && k.hasNext()) {
+ QVariant j_value = j.next();
+ QString k_value = k.next();
+ QVariantMap valueMap;
+ // Strip hierarchical components
+ if (k_value.contains("."))
+ valueMap.insert(k_value.split(".").last(), j_value);
+ else
+ valueMap.insert(k_value, j_value);
+ queryList.append(QVariant(valueMap));
+ }
+ }
+
Q_FOREACH (QVariantMap mapIdResult, results) {
QString docId((mapIdResult["docId"]).toString());
QVariant result_variant(mapIdResult["result"]);
@@ -135,10 +163,9 @@
j.next();
- bool tmp_match = queryField(j.key(), j.value());
-
- if(tmp_match == false){
+ if (!iterateQueryList(queryList, j.key(), j.value())) {
match = false;
+ break;
}
}
@@ -158,7 +185,6 @@
Q_EMIT documentsChanged(m_documents);
Q_EMIT resultsChanged(m_results);
-
}
/*!
@@ -175,87 +201,41 @@
/*!
\internal
- Query a single field.
- */
-bool Query::queryField(QString field, QVariant value){
-
- bool match = false;
-
- QVariant query = getQuery();
- // * is the default if query is empty
- if (!query.isValid())
- query = QVariant(QString("*"));
- QString typeName = query.typeName();
-
- if(typeName == "QString")
- {
- QString query_string = query.toString();
- match = queryString(query_string, value);
- }
- else if(typeName == "int")
- {
- QString query_string = query.toString();
- match = queryString(query_string, value);
- }
- else if(typeName == "QVariantList")
- {
- match = iterateQueryList(query, field, value);
- }
- else
- {
- m_query = "";
- qWarning("u1db: Unexpected type %s for query", qPrintable(typeName));
- }
-
- return match;
-
-}
-
-/*!
- \internal
Loop through the query assuming it's a list.
+
+ For example:
+ queryList: { type: 'show' }
+ field: 'type'
+ value: 'show'
*/
-bool Query::iterateQueryList(QVariant query, QString field, QVariant value)
+bool Query::iterateQueryList(QVariantList queryList, QString field, QVariant value)
{
-
- bool match = false;
-
- QList<QVariant> query_list = query.toList();
- QListIterator<QVariant> j(query_list);
+ QListIterator<QVariant> j(queryList);
while (j.hasNext()) {
-
QVariant j_value = j.next();
-
- QString typeName = j_value.typeName();
-
- if(typeName == "QVariantMap")
- {
- match = queryMap(j_value.toMap(), value.toString(), field);
-
- if(match == true){
- break;
- }
-
- }
- else if(typeName == "QString"){
-
- match = queryString(j_value.toString(), value);
-
- if(match == true){
- break;
- }
-
- }
- else
- {
- m_query = "";
- qWarning("u1db: Unexpected type %s for query", qPrintable(typeName));
- }
-
+ QVariantMap valueMap(j_value.toMap());
+ if (!queryMap(valueMap, value.toString(), field))
+ return false;
}
- return match;
+ return true;
+}
+
+/*!
+ \internal
+ Verify that query is an identical or wild card match.
+ */
+bool Query::queryMatchesValue(QString query, QString value)
+{
+ if (query == "*")
+ return true;
+ if (query == value)
+ return true;
+ if (!query.contains ("*"))
+ return false;
+ QString prefix(query.split("*")[0]);
+ return value.startsWith(prefix, Qt::CaseSensitive);
}
/*!
@@ -274,36 +254,20 @@
return false;
}
- bool match = false;
-
- if(query == "*"){
- return true;
- }
- else if(query == value){
- return true;
- }
- else if(query.contains("*")){
- QStringList k_string_list = query.split("*");
- QString k_string = k_string_list[0];
- match = value.toString().startsWith(k_string,Qt::CaseSensitive);
-
- return match;
-
- }
-
-
- return match;
+ return queryMatchesValue(query, value.toString());
}
/*!
\internal
Loop through the given map of keys and queries.
+
+ For example:
+ map: { type: 'show' }
+ value: { 'show' }
+ field: 'type'
*/
bool Query::queryMap(QVariantMap map, QString value, QString field)
{
-
- bool match = false;
-
QMapIterator<QString,QVariant> k(map);
while(k.hasNext()){
@@ -314,24 +278,12 @@
QString query = k_variant.toString();
if(field == k_key){
-
- if(query == "*"){
- return true;
- }
- else if(query == value){
- return true;
- }
- else if(query.contains("*")){
- QStringList k_string_list = query.split("*");
- QString k_string = k_string_list[0];
- match = value.startsWith(k_string,Qt::CaseSensitive);
- return match;
- }
-
+ if (!queryMatchesValue(query, value))
+ return false;
}
}
- return match;
+ return true;
}
/*!
=== 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 @@
void onDataInvalidated();
+ bool debug();
void generateQueryResults();
- bool iterateQueryList(QVariant query, QString field, QVariant value);
+ bool iterateQueryList(QVariantList list, QString field, QVariant value);
+ bool queryMatchesValue(QString query, QString value);
bool queryString(QString query, QVariant value);
bool queryMap(QVariantMap map, QString value, QString field);
bool queryField(QString field, QVariant value);
=== 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 @@
U1db.Database {
id: gents
- path: 'aDatabaseU'
}
U1db.Document {
@@ -47,6 +46,18 @@
contents: { 'misc': { 'software': 'linux', 'sports': [ 'basketball', 'hockey' ] }, 'date': '2014-01-01' , 'gents': [ { 'name': 'Ivanka', 'phone': 00321 }, ] }
}
+ U1db.Document {
+ database: gents
+ docId: 'F'
+ contents: { 'details': { 'name': 'spy', 'type': 'hide', 'colour': 'blue' } }
+ }
+
+ U1db.Document {
+ database: gents
+ docId: 'G'
+ contents: { 'details': { 'name': 'kid', 'type': 'show', 'colour': 'green' } }
+ }
+
U1db.Index {
id: byPhone
database: gents
@@ -100,6 +111,12 @@
U1db.Query {
id: i12345Phone
index: byPhone
+ query: [ { 'phone': 12345 } ]
+ }
+
+ U1db.Query {
+ id: i12345PhoneSimple
+ index: byPhone
query: 12345
}
@@ -112,6 +129,12 @@
U1db.Query {
id: ivankaAllNamePhone
index: byNamePhone
+ query: [ { name: 'Ivanka', phone: '*' } ]
+ }
+
+ U1db.Query {
+ id: ivankaAllNamePhoneSimple
+ index: byNamePhone
query: ['Ivanka', '*']
}
@@ -122,17 +145,96 @@
}
U1db.Query {
- id: wrongQuery
- index: byNamePhone
- query: [{ 'name': 'Ivanka', 'phone': '*' }]
- }
-
- U1db.Query {
id: toplevelQuery
index: byDate
query: [{ 'date': '2014*', 'sports': 'basketball', 'software': 'linux' }]
}
+ U1db.Query {
+ id: toplevelQuerySimple
+ index: byDate
+ query: [ '2014*', 'basketball', 'linux' ]
+ }
+
+ U1db.Query {
+ id: queryOne
+ index: U1db.Index {
+ database: gents
+ name: 'one'
+ expression: [ 'details.type' ]
+ }
+ query: [ 'show' ]
+ }
+
+ U1db.Query {
+ id: queryBothSimple
+ index: U1db.Index {
+ database: gents
+ name: 'bothSimple'
+ expression: [ 'details.type', 'details.colour' ]
+ }
+ query: [ 'show', '*' ]
+ }
+
+ U1db.Query {
+ id: queryBoth
+ index: U1db.Index {
+ database: gents
+ name: 'both'
+ expression: [ 'details.type', 'details.colour' ]
+ }
+ query: [ { type: 'show', colour: '*' } ]
+ }
+
+ U1db.Database {
+ id: tokusatsu
+ }
+
+ U1db.Document {
+ database: tokusatsu
+ docId: 'ooo'
+ contents: { 'series': 'ooo', 'type': 'rider' }
+ }
+
+ U1db.Document {
+ database: tokusatsu
+ docId: 'gokaiger'
+ contents: { 'series': 'gokaiger', 'type': 'sentai' }
+ }
+
+ U1db.Document {
+ id: tokusatsuDocumentWizard
+ docId: 'wizard'
+ contents: { 'series': 'wizard', 'type': 'rider',
+ 'transformations': ['Flame Style','Water Style'] }
+ }
+
+ U1db.Document {
+ id: tokusatsuDocumentDino
+ docId: 'dino'
+ contents: { 'series': 'zyuranger', 'scarf': false, 'type': 'sentai',
+ 'beasts': ['T-Rex', 'Mastodon'] }
+ }
+
+ U1db.Index {
+ id: bySeries
+ database: tokusatsu
+ name: 'by-series'
+ expression: ['series', 'type']
+ }
+
+ U1db.Query {
+ id: allHeroesWithType
+ index: bySeries
+ query: [{ 'series': '*' }, { 'type': '*' }]
+ }
+
+ U1db.Query {
+ id: allHeroesSeriesOnly
+ index: bySeries
+ query: [{ 'series': '*' }]
+ }
+
SignalSpy {
id: spyDocumentsChanged
target: defaultPhone
@@ -154,7 +256,11 @@
return A
}
- function compare (a, b) {
+ function compareFail(a, b, msg) {
+ compare(a, b, msg, true)
+ }
+
+ function compare(a, b, msg, willFail) {
/* Override built-in compare to:
Match different JSON for identical values (number hash versus list)
Produce readable output for all JSON values
@@ -163,8 +269,14 @@
return
var A = prettyJson(a), B = prettyJson(b)
if (A != B) {
- fail('%1 != %2 (%3 != %4)'.arg(A).arg(B).arg(JSON.stringify(a)).arg(JSON.stringify(b)))
+ if (willFail) {
+ console.log('Expected failure: %1%2 != %3'.arg(msg ? msg + ': ' : '').arg(A).arg(B))
+ return
+ }
+ fail('%5%1 != %2 (%3 != %4)'.arg(A).arg(B).arg(JSON.stringify(a)).arg(JSON.stringify(b)).arg(msg ? msg + ': ' : ''))
}
+ if (willFail)
+ 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 + ': ' : ''))
}
function workaroundQueryAndWait (buggyQuery) {
@@ -173,21 +285,12 @@
spyDocumentsChanged.wait();
}
- function test_0_wrongUse () {
- workaroundQueryAndWait(wrongQuery)
- ignoreWarning('u1db: Unexpected type QVariantMap for query')
- wrongQuery.query = { 'name': 'Ivanka' }
- ignoreWarning('u1db: Unexpected type QObject* for query')
- wrongQuery.query = defaultPhone
- }
-
function test_1_defaults () {
// We should get all documents
workaroundQueryAndWait(defaultPhone)
compare(defaultPhone.documents, ['1', '_', 'a'], 'uno')
compare(defaultPhone.results.length, 3, 'dos')
compare(defaultPhone.results.length, defaultPhone.documents.length, 'puntos')
- // FIXME: compare(defaultPhone.results, [], 'dos')
// These queries are functionally equivalent
compare(defaultPhone.documents, allPhone.documents, 'tres')
compare(defaultPhone.documents, allPhoneList.documents, 'quatro')
@@ -205,18 +308,21 @@
compare(s12345Phone.documents, ['1'], 'uno')
// It's okay to mix strings and numerical values
compare(s12345Phone.documents, i12345Phone.documents, 'dos')
+ compare(i12345PhoneSimple.documents, i12345Phone.documents, 'tres')
}
function test_3_wildcards () {
// Trailing string wildcard
compare(s1wildcardPhone.documents, ['1'], 'uno')
// Last given field can use wildcards
- // FIXME: compare(ivankaAllNamePhone.documents, ['_', 'a'], 'dos')
+ compare(ivankaAllNamePhoneSimple.documents, ['_', 'a'], 'dos')
+ compare(ivankaAllNamePhone.documents, ['_', 'a'], 'tres')
// These queries are functionally equivalent
workaroundQueryAndWait(ivankaAllNamePhoneKeywords)
workaroundQueryAndWait(ivankaAllNamePhone)
- // FIXME: compare(ivankaAllNamePhone.documents, ivankaAllNamePhoneKeywords.documents, 'tres')
+ compare(ivankaAllNamePhone.documents, ivankaAllNamePhoneKeywords.documents, 'tres')
compare(toplevelQuery.documents, ['_'])
+ compare(toplevelQuerySimple.documents, ['_'], 'cinco')
}
function test_4_delete () {
@@ -226,5 +332,29 @@
compare(defaultPhone.documents, ['1', 'a'], 'dos')
}
+ function test_5_fields () {
+ compare(queryOne.documents, ['G'], 'one field')
+ compare(queryBoth.documents, ['G'], 'two fields')
+ compare(queryBothSimple.documents, ['G'], 'two fields simple')
+ }
+
+ function test_6_definition () {
+ workaroundQueryAndWait(allHeroesWithType)
+ compare(allHeroesWithType.documents, ['gokaiger', 'ooo'], 'ichi')
+ workaroundQueryAndWait(allHeroesSeriesOnly)
+ compare(allHeroesSeriesOnly.documents, ['gokaiger', 'ooo'], 'ni')
+ compare(allHeroesWithType.documents, allHeroesSeriesOnly.documents, 'doube-check')
+ // Add a document with extra fields
+ tokusatsu.putDoc(tokusatsuDocumentWizard.contents, tokusatsuDocumentWizard.docId)
+ workaroundQueryAndWait(allHeroesWithType)
+ compare(allHeroesWithType.documents, ['gokaiger', 'ooo', 'wizard'], 'san')
+ workaroundQueryAndWait(allHeroesSeriesOnly)
+ compare(allHeroesWithType.documents, allHeroesSeriesOnly.documents, 'chi')
+ // Add a document with mixed custom fields
+ tokusatsu.putDoc(tokusatsuDocumentDino.contents, tokusatsuDocumentDino.docId)
+ workaroundQueryAndWait(allHeroesWithType)
+ compare(allHeroesWithType.documents, ['dino', 'gokaiger', 'ooo', 'wizard'], 'go')
+ compare(allHeroesWithType.documents, allHeroesSeriesOnly.documents, 'roku')
+ }
} }
References