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

Proposed by Cris Dywan
Status: Needs review
Proposed branch: lp:~kalikiana/u1db-qt/indexRoles
Merge into: lp:u1db-qt
Diff against target: 485 lines (+349/-8)
6 files modified
debian/control (+2/-0)
examples/bookmarks/bookmarks.qml (+6/-3)
examples/movies/movies.qml (+196/-0)
src/query.cpp (+12/-2)
tests/test-database.cpp (+15/-0)
tests/tst_query.qml (+118/-3)
To merge this branch: bzr merge lp:~kalikiana/u1db-qt/indexRoles
Reviewer Review Type Date Requested Status
PS Jenkins bot continuous-integration Approve
Benjamin Zeller Pending
Review via email: mp+211771@code.launchpad.net

Commit message

Expose index fields as role names

Description of the change

This is aimed to work with lp:~kalikiana/ubuntu-ui-toolkit/sortFilterModel.
TODO: Before merging we should have a unit test for it.

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
Andrew Hayzen (ahayzen) wrote :

FYI the branch which I was having issues sorting can be found here [0].

0 - https://code.launchpad.net/~andrew-hayzen/+junk/u1db-sortfiltermodel-recent-test

lp:~kalikiana/u1db-qt/indexRoles updated
118. By Cris Dywan

Add sort and filter unit test cases and movies example

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
lp:~kalikiana/u1db-qt/indexRoles updated
119. By Cris Dywan

Build and examples should depend on UI toolkit plugin

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
lp:~kalikiana/u1db-qt/indexRoles updated
120. By Cris Dywan

Merge lp:u1db-qt

121. By Cris Dywan

Add C++ unit test checking role names of Query

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
lp:~kalikiana/u1db-qt/indexRoles updated
122. By Cris Dywan

Work-around for sort.property not correctly re-sorting

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

Unmerged revisions

122. By Cris Dywan

Work-around for sort.property not correctly re-sorting

121. By Cris Dywan

Add C++ unit test checking role names of Query

120. By Cris Dywan

Merge lp:u1db-qt

119. By Cris Dywan

Build and examples should depend on UI toolkit plugin

118. By Cris Dywan

Add sort and filter unit test cases and movies example

117. By Cris Dywan

Expose index fields as role names

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'debian/control'
2--- debian/control 2014-01-22 13:14:22 +0000
3+++ debian/control 2014-11-12 17:08:18 +0000
4@@ -14,6 +14,7 @@
5 qtdeclarative5-qtquick2-plugin,
6 qtdeclarative5-test-plugin,
7 qttools5-dev-tools,
8+ qtdeclarative5-ubuntu-ui-toolkit-plugin,
9 ubuntu-ui-toolkit-doc,
10 xvfb,
11 libgl1-mesa-dri,
12@@ -65,6 +66,7 @@
13 Section: doc
14 Architecture: all
15 Depends: libu1db-qt5-3 (>= ${source:Version}),
16+ qtdeclarative5-ubuntu-ui-toolkit-plugin,
17 ${misc:Depends},
18 Description: Qt5 binding and QtQuick2 plugin for U1DB - examples
19 Simple Qt5 binding and QtQuick2 plugin for U1DB (https://launchpad.net/u1db).
20
21=== modified file 'examples/bookmarks/bookmarks.qml'
22--- examples/bookmarks/bookmarks.qml 2014-02-17 17:40:42 +0000
23+++ examples/bookmarks/bookmarks.qml 2014-11-12 17:08:18 +0000
24@@ -123,8 +123,8 @@
25 property string detailsDocId: ""
26 property string detailsContents: ""
27 delegate: ListItem.Subtitled {
28- text: contents.title || '[title:%1]'.arg(docId)
29- subText: contents.uri || '[uri:%1]'.arg(docId)
30+ text: title
31+ subText: uri
32 // iconSource: contents.uri + "/favicon.ico"
33 fallbackIconName: "favorite-unselected,text-html"
34 iconFrame: false
35@@ -147,7 +147,7 @@
36 ListElement { label: 'Ubuntu'; expression: "[ 'uri' ]"; query: "[ 'http://www.ubuntu*' ]" }
37 ListElement { label: 'Search'; expression: "[ 'meta.title' ]"; query: "[ 'Search*' ]" }
38 ListElement { label: 'Engine'; expression: "[ 'meta.tags' ]"; query: "[ 'engine' ]" }
39- ListElement { label: 'All'; expression: "[ 'meta.visits', 'meta.title' ]"; query: "[ '*', '*' ]" }
40+ ListElement { label: 'All'; expression: "[ 'meta.visits', 'meta.title', 'uri' ]"; query: "[ '*', '*', '*' ]" }
41 }
42 delegate: OptionSelectorDelegate {
43 text: i18n.tr(label)
44@@ -156,8 +156,11 @@
45 onSelectedIndexChanged: {
46 var d = model.get(selectedIndex)
47 text = '%1 - %2'.arg(d.expression).arg(d.query)
48+ bookmarksList.model = null
49 allBookmarks.index.expression = eval(d.expression)
50 allBookmarks.query = eval(d.query)
51+ // Force update of delegates and role names
52+ bookmarksList.model = allBookmarks
53 }
54 }
55 }
56
57=== added directory 'examples/movies'
58=== added file 'examples/movies/movies.qml'
59--- examples/movies/movies.qml 1970-01-01 00:00:00 +0000
60+++ examples/movies/movies.qml 2014-11-12 17:08:18 +0000
61@@ -0,0 +1,196 @@
62+/*
63+ * Copyright (C) 2014 Canonical, Ltd.
64+ *
65+ * Authors:
66+ * Christian Dywan <christian.dywan@canonical.com>
67+ *
68+ * This program is free software; you can redistribute it and/or modify
69+ * it under the terms of the GNU Lesser General Public License as published by
70+ * the Free Software Foundation; version 3.
71+ *
72+ * This program is distributed in the hope that it will be useful,
73+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
74+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
75+ * GNU Lesser General Public License for more details.
76+ *
77+ * You should have received a copy of the GNU Lesser General Public License
78+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
79+ */
80+
81+import QtQuick 2.0
82+import U1db 1.0 as U1db
83+import Ubuntu.Components 1.1
84+import Ubuntu.Components.ListItems 0.1 as ListItem
85+import Ubuntu.Components.Popups 0.1
86+
87+MainView {
88+ id: root
89+ applicationName: "com.ubuntu.developer.foobar.movies"
90+
91+ width: units.gu(45)
92+ height: units.gu(80)
93+
94+ /*
95+ Movie database
96+ */
97+
98+ U1db.Database {
99+ id: db
100+ // path: "movies.db"
101+ }
102+
103+ // Defaults taken from http://archive.blender.org/features-gallery/movies/index.html
104+
105+ U1db.Document {
106+ database: db
107+ docId: 'yBigBuckBunny'
108+ create: true
109+ defaults: {
110+ 'title': 'Big Buck Bunny (Short film)',
111+ producer: 'Blender Foundation',
112+ year: 2008, duration: '10min', website: 'https://www.bigbugckbunny.org'
113+ }
114+ }
115+
116+ U1db.Document {
117+ database: db
118+ docId: 'zElephantsDream'
119+ create: true
120+ defaults: {
121+ 'title': 'Elephants Dream (Short film)',
122+ producer: 'Blender Foundation',
123+ year: 2006, duration: '10min', website: 'https://www.elephantsdream.org'
124+ }
125+ }
126+
127+ U1db.Document {
128+ database: db
129+ docId: 'cProjectLondon'
130+ create: true
131+ defaults: {
132+ 'title': 'Project London Trailer',
133+ producer: 'Independent',
134+ year: 2009, duration: '22sec', website: 'https://www.projectlondonmovie.com'
135+ }
136+ }
137+
138+ U1db.Document {
139+ database: db
140+ docId: 'aSoftBoy'
141+ create: true
142+ defaults: {
143+ 'title': 'Soft Boy (Animation)',
144+ producer: 'Andy Goralczyk',
145+ year: 2005, duration: '17sec'
146+ }
147+ }
148+
149+ U1db.Document {
150+ database: db
151+ docId: 'bEsign'
152+ create: true
153+ defaults: {
154+ 'title': 'Esign (Animation)',
155+ producer: 'Chris Larkee',
156+ year: 2005, duration: '4min'
157+ }
158+ }
159+
160+ U1db.Query {
161+ id: allMovies
162+ index: blacklist
163+ query: [ { year: '*', producer: '*', duration: '*', title: '*' } ]
164+ }
165+
166+ U1db.Index {
167+ id: blacklist
168+ database: db
169+ expression: [ 'year', 'duration', 'title', 'producer' ]
170+ }
171+
172+ SortFilterModel {
173+ id: sortedMovies
174+ model: allMovies
175+ filter.pattern: RegExp(searchProducer.text)
176+ filter.property: 'producer'
177+ sort.property: sortBy.role
178+ sort.order: Qt.DescendingOrder
179+ }
180+
181+ /*
182+ UI: list view, filters
183+ */
184+
185+ Page {
186+ id: page
187+ title: i18n.tr('Movies')
188+
189+ Column {
190+ id: container
191+ anchors.margins: units.gu(1)
192+ anchors.fill: parent
193+
194+ ListView {
195+ id: movieList
196+ width: parent.width
197+ height: parent.height - pillar.height
198+ model: sortedMovies
199+ delegate: ListItem.Subtitled {
200+ text: '<b>%1</b> %2'.arg(title).arg(year)
201+ subText: '%1 <b>%2</b>'.arg(duration).arg(producer)
202+ fallbackIconName: "favorite-unselected,text-html"
203+ iconFrame: false
204+ }
205+ }
206+
207+ Column {
208+ id: pillar
209+ spacing: units.gu(1)
210+ width: parent.width
211+
212+ Label {
213+ text: sortBy.role
214+ }
215+
216+ OptionSelector {
217+ id: sortBy
218+ StateSaver.properties: 'selectedIndex'
219+ model: ListModel {
220+ ListElement { label: 'Title'; role: 'title' }
221+ ListElement { label: 'Year'; role: 'year' }
222+ ListElement { label: 'Length'; role: 'duration' }
223+ }
224+ delegate: OptionSelectorDelegate {
225+ text: i18n.tr(label)
226+ }
227+ property string role: 'title'
228+ onDelegateClicked: {
229+ var selectedIndex = index
230+ role = model.get(selectedIndex).role
231+ // Work-around for sort.property not correctly re-sorting
232+ var m = movieList.model
233+ movieList.model = null
234+ movieList.model = m
235+ }
236+ }
237+
238+ TextField {
239+ id: searchProducer
240+ StateSaver.properties: 'text'
241+ placeholderText: i18n.tr('Search Producer…')
242+ }
243+
244+ OptionSelector {
245+ id: selectYear
246+ StateSaver.properties: 'selectedIndex'
247+ model: [ '*', 2005, 2006, 2007, 2008, 2009 ]
248+ delegate: OptionSelectorDelegate {
249+ text: modelData == '*' ? 'All Years' : modelData
250+ }
251+ property var year: model[selectedIndex]
252+ }
253+ }
254+ }
255+ }
256+}
257+
258
259=== modified file 'src/query.cpp'
260--- src/query.cpp 2014-03-13 19:31:58 +0000
261+++ src/query.cpp 2014-11-12 17:08:18 +0000
262@@ -17,7 +17,6 @@
263 * along with this program. If not, see <http://www.gnu.org/licenses/>.
264 */
265
266-#include <QDebug>
267 #include <QSqlQuery>
268 #include <QFile>
269 #include <QSqlError>
270@@ -64,7 +63,11 @@
271 return m_results.at(index.row());
272 if (role == 1) // docId
273 return m_documents.at(index.row());
274- return QVariant();
275+
276+ int indexForRole = role - 2;
277+ QVariantMap result(m_results.at(index.row()).toMap());
278+ QString field(m_index->getExpression().at(indexForRole).split(".").last());
279+ return result.value(field);
280 }
281
282 /*!
283@@ -79,6 +82,13 @@
284 QHash<int, QByteArray> roles;
285 roles.insert(0, "contents");
286 roles.insert(1, "docId");
287+ if (!m_index)
288+ return roles;
289+
290+ // A role name for each field in the index
291+ uint fieldRole = 2;
292+ Q_FOREACH(QString field, m_index->getExpression())
293+ roles.insert(fieldRole++, field.split(".").last().toUtf8());
294 return roles;
295 }
296
297
298=== modified file 'tests/test-database.cpp'
299--- tests/test-database.cpp 2014-11-07 14:30:31 +0000
300+++ tests/test-database.cpp 2014-11-12 17:08:18 +0000
301@@ -82,6 +82,21 @@
302 query.setQuery(QStringList() << "2014*" << "basketball" << "linux");
303 }
304
305+ void testRoleNames()
306+ {
307+ Database db;
308+ Index index;
309+ index.setDatabase(&db);
310+ index.setExpression(QStringList() << "species" << "traits.spots");
311+ Query query;
312+ query.setIndex(&index);
313+ QStringList expected(QStringList() << "docId" << "contents" << "species" << "spots");
314+ Q_FOREACH(QString roleName, expected)
315+ QVERIFY2(query.roleNames().values().contains(roleName.toUtf8()),
316+ qPrintable(QString("Expected role name \"%1\" not in Query").arg(roleName)));
317+ QVERIFY(db.lastError().isEmpty());
318+ }
319+
320 void cleanupTestCase()
321 {
322 }
323
324=== modified file 'tests/tst_query.qml'
325--- tests/tst_query.qml 2014-03-13 19:31:58 +0000
326+++ tests/tst_query.qml 2014-11-12 17:08:18 +0000
327@@ -20,6 +20,8 @@
328 import QtQuick 2.0
329 import QtTest 1.0
330 import U1db 1.0 as U1db
331+import Ubuntu.Components 1.1
332+import Ubuntu.Test 0.1
333
334 Item {
335 width: 200; height: 200
336@@ -241,13 +243,79 @@
337 signalName: "documentsChanged"
338 }
339
340-TestCase {
341+ U1db.Database {
342+ id: animals
343+ }
344+
345+ // Note: docId's intentionally chosen for wrong alphabetic order
346+ U1db.Document {
347+ database: animals
348+ docId: 'k_cow'
349+ contents: { 'species': 'cow', 'traits': [ { 'spots': true } ] }
350+ }
351+ U1db.Document {
352+ database: animals
353+ docId: 'z_ant'
354+ contents: { 'species': 'ant' }
355+ }
356+
357+ U1db.Document {
358+ database: animals
359+ docId: 'a_bee'
360+ contents: { 'species': 'bee' }
361+ }
362+
363+ U1db.Document {
364+ database: animals
365+ docId: 'n_cat'
366+ contents: { 'species': 'cat' }
367+ }
368+ U1db.Index {
369+ id: bySpecies
370+ database: animals
371+ name: 'by-species'
372+ expression: [ 'species' ]
373+ }
374+
375+ U1db.Query {
376+ id: allAnimals
377+ index: bySpecies
378+ query: [{ 'species': '*' }]
379+ }
380+
381+ // To double-check failures aren't due to SortFilterModel itself
382+ ListModel {
383+ id: fakeAnimals
384+ ListElement { species: 'cow'; docId: 'k_cow' }
385+ ListElement { species: 'ant'; docId: 'z_ant' }
386+ ListElement { species: 'bee'; docId: 'a_bee' }
387+ ListElement { species: 'cat'; docId: 'n_cat' }
388+ }
389+
390+ SortFilterModel {
391+ id: mixedAnimals
392+ model: allAnimals
393+ }
394+
395+ SortFilterModel {
396+ id: sortedAnimals
397+ sort.property: 'species'
398+ sort.order: Qt.AscendingOrder
399+ }
400+
401+ SortFilterModel {
402+ id: filteredAnimals
403+ filter.property: 'species'
404+ filter.pattern: /ant/
405+ }
406+
407+UbuntuTestCase {
408 name: "U1dbDatabase"
409 when: windowShown
410
411 function prettyJson(j) {
412 var A = JSON.stringify(j)
413- if (A['0'] && A != '{}') {
414+ if (A != undefined && A['0'] && A != '{}') {
415 var A = '['
416 for(var i in j)
417 A += JSON.stringify(j[i]) + ','
418@@ -260,6 +328,8 @@
419 compare(a, b, msg, true)
420 }
421
422+ TestUtil { id: util }
423+
424 function compare(a, b, msg, willFail) {
425 /* Override built-in compare to:
426 Match different JSON for identical values (number hash versus list)
427@@ -273,7 +343,12 @@
428 console.log('Expected failure: %1%2 != %3'.arg(msg ? msg + ': ' : '').arg(A).arg(B))
429 return
430 }
431- fail('%5%1 != %2 (%3 != %4)'.arg(A).arg(B).arg(JSON.stringify(a)).arg(JSON.stringify(b)).arg(msg ? msg + ': ' : ''))
432+ if (!qtest_results.compare (false,
433+ msg ? msg : "Comparison failed",
434+ A + " ~ " + JSON.stringify(a),
435+ B + " ~ " + JSON.stringify(b),
436+ util.callerFile(), util.callerLine()))
437+ throw new Error("QtQuickTest::fail")
438 }
439 if (willFail)
440 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 + ': ' : ''))
441@@ -359,5 +434,45 @@
442 compare(allHeroesWithType.documents, ['dino', 'gokaiger', 'ooo', 'wizard'], 'go')
443 compare(allHeroesWithType.documents, allHeroesSeriesOnly.documents, 'roku')
444 }
445+
446+ function test_7_sort_data () {
447+ return [ { tag: 'Reference ListModel', model: fakeAnimals },
448+ { tag: 'U1db.Query', model: allAnimals } ]
449+ }
450+
451+ function test_7_sort (data) {
452+ // Sanity checks
453+ compare(allAnimals.documents, ["a_bee", "k_cow", "n_cat", "z_ant"])
454+ tryCompareFunction(function(){return mixedAnimals.get(0).docId}, "a_bee")
455+
456+ sortedAnimals.model = data.model
457+ compare(sortedAnimals.count, 4, "number of animals")
458+ sortedAnimals.sort.property = 'species'
459+ compare(sortedAnimals.get(0).docId, "z_ant", "ants document sorted first")
460+ compare(sortedAnimals.get(0).species, "ant", "ants species sorted first")
461+
462+ sortedAnimals.sort.order = Qt.DescendingOrder
463+ compare(sortedAnimals.get(0).species, "cow", "cow sorted first descending")
464+
465+ sortedAnimals.sort.property = 'docId'
466+ sortedAnimals.sort.order = Qt.AscendingOrder
467+ compare(sortedAnimals.get(0).docId, "a_bee", "bee sorted first by docId")
468+ }
469+
470+ function test_8_filter_data () {
471+ return [ { tag: 'Reference ListModel', model: fakeAnimals },
472+ { tag: 'U1db.Query', model: allAnimals } ]
473+ }
474+
475+ function test_8_filter (data) {
476+ // Sanity checks
477+ compare(allAnimals.documents, ["a_bee", "k_cow", "n_cat", "z_ant"])
478+ tryCompareFunction(function(){return mixedAnimals.get(0).docId}, "a_bee")
479+
480+ filteredAnimals.model = data.model
481+ compare(filteredAnimals.count, 1, "number of animals")
482+ compare(filteredAnimals.get(0).species, "ant", "ants species role available")
483+ compare(filteredAnimals.get(0).docId, "z_ant", "ants included in filter")
484+ }
485 } }
486

Subscribers

People subscribed via source and target branches

to all changes: