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

Proposed by Cris Dywan
Status: Merged
Approved by: Cris Dywan
Approved revision: 116
Merged at revision: 112
Proposed branch: lp:~kalikiana/u1db-qt/queryLog
Merge into: lp:u1db-qt
Diff against target: 849 lines (+195/-488)
7 files modified
examples/CMakeLists.txt (+1/-1)
examples/bookmarks/bookmarks.qml (+131/-450)
examples/u1db-qt-example-4/u1db-qt-example-4.qdoc (+0/-24)
src/index.cpp (+6/-2)
src/query.cpp (+16/-8)
src/query.h (+2/-2)
tests/tst_query.qml (+39/-1)
To merge this branch: bzr merge lp:~kalikiana/u1db-qt/queryLog
Reviewer Review Type Date Requested Status
Benjamin Zeller Approve
PS Jenkins bot continuous-integration Approve
Review via email: mp+204327@code.launchpad.net

Commit message

Query improvements and more advanced example

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
lp:~kalikiana/u1db-qt/queryLog updated
112. By Cris Dywan

Override built-in compare to resolve matching and JSON output

113. By Cris Dywan

Add unit tests for top level fields and list in hash

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

Fix glob for installing all examples

115. By Cris Dywan

Merge lp:u1db-qt

116. By Cris Dywan

Use deleteDoc to delete document from the bookmarks example

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 :

Looks good, ok to merge

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'examples/CMakeLists.txt'
2--- examples/CMakeLists.txt 2013-04-12 13:52:00 +0000
3+++ examples/CMakeLists.txt 2014-02-17 17:41:08 +0000
4@@ -1,4 +1,4 @@
5-file(GLOB ALL_EXAMPLES *-*/*.qml)
6+file(GLOB ALL_EXAMPLES */*.qml)
7 install(FILES ${ALL_EXAMPLES}
8 DESTINATION ${CMAKE_INSTALL_PREFIX}/share/u1db-qt/examples
9 )
10
11=== renamed directory 'examples/u1db-qt-example-4' => 'examples/bookmarks'
12=== renamed file 'examples/u1db-qt-example-4/u1db-qt-example-4.qml' => 'examples/bookmarks/bookmarks.qml'
13--- examples/u1db-qt-example-4/u1db-qt-example-4.qml 2013-05-02 18:19:33 +0000
14+++ examples/bookmarks/bookmarks.qml 2014-02-17 17:41:08 +0000
15@@ -1,8 +1,8 @@
16 /*
17- * Copyright (C) 2013 Canonical, Ltd.
18+ * Copyright (C) 2014 Canonical, Ltd.
19 *
20 * Authors:
21- * Kevin Wright <kevin.wright@canonical.com>
22+ * Christian Dywan <christian.dywan@canonical.com>
23 *
24 * This program is free software; you can redistribute it and/or modify
25 * it under the terms of the GNU Lesser General Public License as published by
26@@ -20,6 +20,8 @@
27 import QtQuick 2.0
28 import U1db 1.0 as U1db
29 import Ubuntu.Components 0.1
30+import Ubuntu.Components.ListItems 0.1 as ListItem
31+import Ubuntu.Components.Popups 0.1
32
33 /*!
34
35@@ -33,452 +35,131 @@
36 */
37
38 MainView {
39- width: units.gu(45)
40- height: units.gu(80)
41-
42- /*!
43-
44- A Database is very simple to create. It only needs an id and a path where the file will be created. A Database is a model, which can be used by elements, such as the ListView further in this example.
45-
46- U1db.Database {
47- id: aDatabase
48- path: "aDatabase4"
49- }
50-
51- */
52-
53- U1db.Database {
54- id: aDatabase
55- path: "aDatabase4"
56- }
57-
58- /*!
59-
60- A Document can be declared at runtime. It requires at the very least a unique 'docId', but that alone won't do anything special. The snipet below snippet demonstrates the basic requirements.
61-
62- In addition to this, this example displays text from the database for a specific docId and id key in a text area called 'documentContent. To update the text area at startup with either the default value or a value from the database the onCompleted function is utilized, which is also demonstrated below.
63-
64- U1db.Document {
65- id: aDocument
66- database: aDatabase
67- docId: 'helloworld'
68- create: true
69- defaults: { "helloworld":"Hello World" }
70-
71- Component.onCompleted: {
72- documentContent.text = aDocument.contents.helloworld
73- }
74-
75- }
76-
77- */
78-
79-
80- U1db.Document {
81- id: aDocument
82- database: aDatabase
83- docId: 'helloworld'
84- create: true
85- defaults: { "helloworld":"Hello World" }
86-
87- Component.onCompleted: {
88- documentContent.text = aDocument.contents.helloworld
89- }
90-
91- }
92-
93- /*!
94-
95- It should be possible to use a document without a database, as demonstrated in this snippet. Additionally this document will use the concept of sub-keys, as exemplified by the "bookmarks" id key + contents. This example will attempt to use the bookmark document to store docId values from the database, which will be displayed in a ListView on the second tab of the application. The user will be able to select a value from the ListView and the first tab will be modified accordingly.
96-
97- U1db.Document {
98- id: aBookmarkDocument
99- docId: 'bookmarks'
100- create: true
101- defaults: { "bookmarks": [{}] }
102- }
103-
104-
105- */
106-
107-
108- U1db.Document {
109- id: aBookmarkDocument
110- docId: 'bookmarks'
111- create: true
112- defaults: { "bookmarks": [{}] }
113- }
114-
115-
116- function switchToPreviousDocument(documentObject){
117-
118- aDocument.docId = getPreviousDocumentId(documentObject)
119-
120- }
121-
122- function switchToNextDocument(){
123-
124- aDocument.docId = getNextDocumentId(aDocument)
125-
126- }
127-
128- function getPreviousDocumentId(documentObject){
129-
130- if(typeof documentObject!='undefined'){
131-
132- /*!
133-
134- The listDocs method retrieves all the docId values from the current database. In this demonstration the values are put into an array, which is then checked to locate the docId for the current and previous documents within the database.
135-
136- var documentIds = {}
137-
138- documentIds = documentObject.database.listDocs()
139-
140- for(var i = 0; i < documentIds.length; i++){
141-
142- if(documentIds[i]===documentObject.docId && i > 0){
143- return documentIds[i-1]
144- }
145- else if(documentIds[i]===documentObject.docId && i==0){
146- return documentIds[documentIds.length-1]
147- }
148-
149- }
150-
151- */
152-
153- var documentIds = {}
154-
155- documentIds = documentObject.database.listDocs()
156-
157- for(var i = 0; i < documentIds.length; i++){
158-
159- if(documentIds[i]===documentObject.docId && i > 0){
160- return documentIds[i-1]
161- }
162- else if(documentIds[i]===documentObject.docId && i==0){
163- return documentIds[documentIds.length-1]
164- }
165-
166- }
167-
168- return documentIds[0]
169-
170- }
171-
172- else{
173-
174- print("Error!")
175-
176- return ''
177- }
178-
179- }
180-
181- function getNextDocumentId(documentObject){
182-
183- if(typeof documentObject!='undefined'){
184-
185- var documentIds = documentObject.database.listDocs()
186-
187- for(var i = 0; i < documentIds.length; i++){
188-
189- if(documentIds[i]===documentObject.docId && i < (documentIds.length-1)){
190- return documentIds[i+1]
191- }
192- else if(documentIds[i]===documentObject.docId && i==(documentIds.length-1)){
193- return documentIds[0]
194- }
195-
196- }
197-
198- return documentIds[0]
199-
200- }
201-
202- else{
203-
204- print("Error!")
205-
206- return ''
207- }
208-
209- }
210-
211- function getCurrentDocumentKey(contentsObject){
212-
213- if(typeof contentsObject!='undefined'){
214-
215- var keys = Object.keys(contentsObject);
216-
217- return keys[0]
218-
219- }
220-
221- else{
222-
223- return ''
224- }
225-
226- }
227-
228- function updateContentWindow(documentText, addressBarText) {
229-
230- // Somewhere below need to check for things like invalid docId
231-
232- if(documentText!==addressBarText) {
233-
234- /*!
235-
236- These steps demonstrate the creation of a temporary document, based on a copy of the global document. This will then be used to determine if there is already a document in the database with the same docId as the address bar, and additionally with a key id with the same name.
237-
238- var tempDocument = {}
239- var tempFieldName = addressBarText;
240- var tempContents = {};
241-
242- tempDocument = aDocument
243- tempDocument.docId = addressBarText;
244-
245- tempContents = tempDocument.contents
246-
247- NOTE: For simplicity sake this example sometimes uses the same value for both the docId and the key id, as seen here. Real life implimentations can and will differ, and this will be demonstrated elsewhere in the example code.
248-
249- */
250-
251- var tempDocument = {}
252- var tempFieldName = addressBarText;
253- var tempContents = {};
254-
255- tempDocument = aDocument
256- tempDocument.docId = addressBarText;
257-
258- tempContents = tempDocument.contents
259-
260- if(typeof tempContents !='undefined' && typeof tempContents[tempFieldName]!='undefined') {
261-
262- aDocument = tempDocument
263- documentContent.text = tempContents[tempFieldName]
264-
265- }
266- else {
267-
268- /*!
269-
270- Here the contents of the temporary document are modified, which then replaces the global document.
271-
272- documentContent.text = 'More Hello World...';
273-
274- tempContents = {}
275- tempContents[tempFieldName] = documentContent.text
276- tempDocument.contents = tempContents
277- aDocument = tempDocument
278-
279- */
280-
281- documentContent.text = 'More Hello World...';
282-
283- tempContents = {}
284- tempContents[tempFieldName] = documentContent.text
285- tempDocument.contents = tempContents
286- aDocument = tempDocument
287-
288- }
289-
290- }
291- else {
292-
293- /*!
294-
295- In this instance the current document's content is updated from the text view. The unique key and docId are not modified because the database already contains a record with those properties.
296-
297- tempContents = {}
298- tempFieldName = getCurrentDocumentKey(aDocument.contents)
299- tempContents[tempFieldName] = documentContent.text
300- aDocument.contents = tempContents
301-
302- */
303-
304- tempContents = {}
305- tempFieldName = getCurrentDocumentKey(aDocument.contents)
306- tempContents[tempFieldName] = documentContent.text
307- aDocument.contents = tempContents
308-
309- }
310-
311- }
312-
313- Tabs {
314- id: tabs
315-
316- Tab {
317- title: i18n.tr("Hello U1Db!")
318-
319- page: Page {
320-
321- id: helloPage
322-
323- /*! Here a rectangle is defined that represents the lower portion of our application. It will contain all the main parts of the application.
324-
325- Rectangle {
326-
327- width: units.gu(45)
328- height: units.gu(70)
329- anchors.bottom: parent.bottom
330-
331- color: "#00FFFFFF"
332-
333- // The remainder of the main part of the application goes here ...
334-
335- }
336-
337- */
338-
339- Rectangle {
340-
341- width: units.gu(45)
342- height: units.gu(70)
343- anchors.fill: parent
344-
345- color: "#00FFFFFF"
346-
347- Rectangle {
348-
349- width: units.gu(45)
350- height: units.gu(60)
351- anchors.bottom: parent.bottom
352-
353- /*!
354-
355- The following TextArea is for displaying contents for the current state of the global document, as defined by the key / name in the address bar.
356-
357- TextArea{
358-
359- id: documentContent
360-
361- selectByMouse : false
362-
363- x: units.gu(1)
364- y: units.gu(1)
365- width: units.gu(43)
366- height: units.gu(58)
367- color: "#000000"
368-
369- }
370-
371- */
372-
373- TextArea {
374-
375- id: documentContent
376-
377- selectByMouse : false
378-
379- x: units.gu(1)
380- y: units.gu(1)
381- width: units.gu(43)
382- height: units.gu(58)
383- color: "#000000"
384-
385- }
386-
387- }
388-
389- // This rectangle contains the navigation controls
390-
391- Rectangle {
392-
393- width: units.gu(43)
394- height: units.gu(5)
395- anchors.top: addressBarArea.bottom
396- x: units.gu(1.5)
397- color: "#00FFFFFF"
398-
399- Row {
400-
401- width: units.gu(43)
402- height: units.gu(5)
403- anchors.verticalCenter: parent.verticalCenter
404- spacing: units.gu(2)
405-
406- Button {
407- text: "<"
408- onClicked: updateContentWindow(switchToPreviousDocument(aDocument), addressBar.text)
409- }
410- Button {
411- text: "Home"
412- onClicked: updateContentWindow(getCurrentDocumentKey(aDocument.contents),'helloworld')
413- }
414- Button {
415- text: "Save"
416- onClicked: updateContentWindow(getCurrentDocumentKey(aDocument.contents),addressBar.text)
417- }
418- Button {
419- text: ">"
420- onClicked: updateContentWindow(switchToNextDocument(aDocument), addressBar.text)
421- }
422-
423- }
424-
425- }
426-
427- Rectangle {
428-
429- id: addressBarArea
430-
431- width: units.gu(45)
432- height: units.gu(5)
433- anchors.top: parent.top
434-
435- TextField {
436-
437- id: addressBar
438-
439- width: units.gu(43)
440- anchors.verticalCenter: parent.verticalCenter
441- x: units.gu(1)
442-
443- hasClearButton: false
444-
445- /*!
446-
447- There is an object within in the 'aDocument' model defined earlier called 'contents', which contains a key called 'hello', which represents a search string. In this example the key will represent the name of a document in the database, which will be displayed in the address bar. Displaying the key is demonstrated here:
448-
449- text: displayKey(aDocument.contents)
450-
451- function displayKey(documentObject){
452-
453- var keys = Object.keys(documentObject);
454-
455- return keys[0]
456-
457- }
458-
459- */
460-
461- text: getCurrentDocumentKey(aDocument.contents)
462-
463-
464- onAccepted: {
465-
466- onClicked: updateContentWindow(getCurrentDocumentKey(aDocument.contents),addressBar.text)
467-
468- }
469- }
470- }
471- }
472- }
473- }
474-
475- Tab {
476- title: i18n.tr("Bookmarks")
477- page: Page {
478- id: bookmarkPage
479-
480- Label {
481- text: aDocument.contents.helloworld
482- }
483-
484- }
485- }
486- }
487+ id: root
488+ applicationName: "com.ubuntu.developer.foobar.bookmarks"
489+
490+ width: units.gu(45)
491+ height: units.gu(80)
492+
493+ /*
494+ Bookmarks database
495+ */
496+
497+ U1db.Database {
498+ id: db
499+ // path: "bookmarks.db"
500+ }
501+
502+ U1db.Document {
503+ database: db
504+ docId: 'defaultsDuckDuckGo'
505+ create: true
506+ defaults: { "uri": "https://www.duckduckgo.com", visited: false, "meta": { "title": "Search DuckDuckGo", visits: 0, tags: [ 'search', 'engine' ] } }
507+ }
508+
509+ U1db.Document {
510+ database: db
511+ docId: 'defaultsUbuntu'
512+ create: true
513+ defaults: { "uri": "http://www.ubuntu.com", visited: true, "meta": { "title": "The world's most popular free OS", visits: 1001 } }
514+ }
515+
516+ U1db.Query {
517+ id: allBookmarks
518+ index: U1db.Index {
519+ database: db
520+ }
521+ }
522+
523+ /*
524+ UI: details
525+ */
526+ Component {
527+ id: detailsPopCom
528+ Popover {
529+ id: detailsPop
530+ Column {
531+ anchors.centerIn: parent
532+ spacing: units.gu(1)
533+ Label {
534+ text: i18n.tr('JSON')
535+ }
536+ TextField {
537+ text: bookmarksList.detailsDocId
538+ }
539+ TextArea {
540+ text: bookmarksList.detailsContents.replace(',',',\n')+'\n\n'
541+ readOnly: true
542+ autoSize: true
543+ }
544+ Button {
545+ text: i18n.tr('Delete')
546+ onClicked: {
547+ PopupUtils.close(detailsPop)
548+ db.deleteDoc(bookmarksList.detailsDocId)
549+ }
550+ }
551+ }
552+ }
553+ }
554+
555+ /*
556+ UI: list view, filters
557+ */
558+
559+ Page {
560+ id: page
561+ title: i18n.tr("Bookmarks")
562+
563+ Item {
564+ id: container
565+ anchors.margins: units.gu(1)
566+ anchors.fill: parent
567+
568+ ListView {
569+ id: bookmarksList
570+ anchors.fill: parent
571+ model: allBookmarks
572+ property string detailsDocId: ""
573+ property string detailsContents: ""
574+ delegate: ListItem.Subtitled {
575+ text: contents.title || '[title:%1]'.arg(docId)
576+ subText: contents.uri || '[uri:%1]'.arg(docId)
577+ // iconSource: contents.uri + "/favicon.ico"
578+ fallbackIconName: "favorite-unselected,text-html"
579+ iconFrame: false
580+ onClicked: {
581+ bookmarksList.detailsDocId = docId
582+ bookmarksList.detailsContents = JSON.stringify(contents)
583+ PopupUtils.open(detailsPopCom, bookmarksList)
584+ }
585+ }
586+ }
587+
588+ OptionSelector {
589+ id: filterSelector
590+ StateSaver.properties: 'selectedIndex'
591+ anchors.bottom: parent.bottom
592+ text: i18n.tr('N/A')
593+ expanded: true
594+ model: ListModel {
595+ ListElement { label: 'Newly Added'; expression: "[ 'meta.visits' ]"; query: "[ { 'visits': 0 } ]" }
596+ ListElement { label: 'Ubuntu'; expression: "[ 'uri' ]"; query: "[ 'http://www.ubuntu*' ]" }
597+ ListElement { label: 'Search'; expression: "[ 'meta.title' ]"; query: "[ 'Search*' ]" }
598+ ListElement { label: 'Engine'; expression: "[ 'meta.tags' ]"; query: "[ 'engine' ]" }
599+ ListElement { label: 'All'; expression: "[ 'meta.visits', 'meta.title' ]"; query: "[ '*', '*' ]" }
600+ }
601+ delegate: OptionSelectorDelegate {
602+ text: i18n.tr(label)
603+ }
604+ selectedIndex: model.count - 1
605+ onSelectedIndexChanged: {
606+ var d = model.get(selectedIndex)
607+ text = '%1 - %2'.arg(d.expression).arg(d.query)
608+ allBookmarks.index.expression = eval(d.expression)
609+ allBookmarks.query = eval(d.query)
610+ }
611+ }
612+ }
613+ }
614 }
615
616=== removed file 'examples/u1db-qt-example-4/u1db-qt-example-4.qdoc'
617--- examples/u1db-qt-example-4/u1db-qt-example-4.qdoc 2013-04-23 11:55:20 +0000
618+++ examples/u1db-qt-example-4/u1db-qt-example-4.qdoc 1970-01-01 00:00:00 +0000
619@@ -1,24 +0,0 @@
620-/*
621- * Copyright (C) 2013 Canonical, Ltd.
622- *
623- * Authors:
624- * Kevin Wright <kevin.wright@canonical.com>
625- *
626- * This program is free software; you can redistribute it and/or modify
627- * it under the terms of the GNU Lesser General Public License as published by
628- * the Free Software Foundation; version 3.
629- *
630- * This program is distributed in the hope that it will be useful,
631- * but WITHOUT ANY WARRANTY; without even the implied warranty of
632- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
633- * GNU Lesser General Public License for more details.
634- *
635- * You should have received a copy of the GNU Lesser General Public License
636- * along with this program. If not, see <http://www.gnu.org/licenses/>.
637- */
638-
639-/*!
640-
641-\page u1db-qt-tutorial-4.html
642-
643-*/
644
645=== modified file 'src/index.cpp'
646--- src/index.cpp 2013-08-08 10:31:16 +0000
647+++ src/index.cpp 2014-02-17 17:41:08 +0000
648@@ -216,7 +216,7 @@
649
650 i.next();
651
652- if(fieldsList.count()>0){
653+ if(!original_field.isEmpty()){
654 current_field = original_field + "." + i.key();
655 }
656 else{
657@@ -235,7 +235,7 @@
658 {
659 fieldsList = getFieldsFromList(docId, fieldsList, value.toList(),current_field);
660 }
661- else
662+
663 {
664 if(m_expression.contains(current_field)==true){
665 results_map.insert(i.key(),value);
666@@ -278,6 +278,10 @@
667 {
668 fieldsList = getFieldsFromList(docId, fieldsList, value.toList(),current_field);
669 }
670+ else if(value.userType()==10) // QString
671+ {
672+ fieldsList.append(current_field);
673+ }
674 else
675 {
676
677
678=== modified file 'src/query.cpp'
679--- src/query.cpp 2013-08-27 10:49:26 +0000
680+++ src/query.cpp 2014-02-17 17:41:08 +0000
681@@ -181,7 +181,6 @@
682
683 bool match = false;
684
685- QString value_string = value.toString();
686 QVariant query = getQuery();
687 // * is the default if query is empty
688 if (!query.isValid())
689@@ -191,16 +190,16 @@
690 if(typeName == "QString")
691 {
692 QString query_string = query.toString();
693- match = queryString(query_string, value_string);
694+ match = queryString(query_string, value);
695 }
696 else if(typeName == "int")
697 {
698 QString query_string = query.toString();
699- match = queryString(query_string, value_string);
700+ match = queryString(query_string, value);
701 }
702 else if(typeName == "QVariantList")
703 {
704- match = iterateQueryList(query, field, value_string);
705+ match = iterateQueryList(query, field, value);
706 }
707 else
708 {
709@@ -216,7 +215,7 @@
710 \internal
711 Loop through the query assuming it's a list.
712 */
713-bool Query::iterateQueryList(QVariant query, QString field, QString value)
714+bool Query::iterateQueryList(QVariant query, QString field, QVariant value)
715 {
716
717 bool match = false;
718@@ -232,7 +231,7 @@
719
720 if(typeName == "QVariantMap")
721 {
722- match = queryMap(j_value.toMap(), value, field);
723+ match = queryMap(j_value.toMap(), value.toString(), field);
724
725 if(match == true){
726 break;
727@@ -263,9 +262,18 @@
728 \internal
729 Handle different types of string values including wildcards.
730 */
731-bool Query::queryString(QString query, QString value)
732+bool Query::queryString(QString query, QVariant value)
733 {
734
735+ QString typeName = value.typeName();
736+ if (typeName == "QVariantList") {
737+ Q_FOREACH (QVariant value_string, value.toList()) {
738+ if (queryString(query, value_string.toString()))
739+ return true;
740+ }
741+ return false;
742+ }
743+
744 bool match = false;
745
746 if(query == "*"){
747@@ -277,7 +285,7 @@
748 else if(query.contains("*")){
749 QStringList k_string_list = query.split("*");
750 QString k_string = k_string_list[0];
751- match = value.startsWith(k_string,Qt::CaseSensitive);
752+ match = value.toString().startsWith(k_string,Qt::CaseSensitive);
753
754 return match;
755
756
757=== modified file 'src/query.h'
758--- src/query.h 2013-06-19 11:35:10 +0000
759+++ src/query.h 2014-02-17 17:41:08 +0000
760@@ -69,8 +69,8 @@
761 void onDataInvalidated();
762
763 void generateQueryResults();
764- bool iterateQueryList(QVariant query, QString field, QString value);
765- bool queryString(QString query, QString value);
766+ bool iterateQueryList(QVariant query, QString field, QVariant value);
767+ bool queryString(QString query, QVariant value);
768 bool queryMap(QVariantMap map, QString value, QString field);
769 bool queryField(QString field, QVariant value);
770 };
771
772=== modified file 'tests/tst_query.qml'
773--- tests/tst_query.qml 2014-01-24 11:13:35 +0000
774+++ tests/tst_query.qml 2014-02-17 17:41:08 +0000
775@@ -44,7 +44,7 @@
776 U1db.Document {
777 database: gents
778 docId: '_'
779- contents: { 'gents': [ { 'name': 'Ivanka', 'phone': 00321 }, ] }
780+ contents: { 'misc': { 'software': 'linux', 'sports': [ 'basketball', 'hockey' ] }, 'date': '2014-01-01' , 'gents': [ { 'name': 'Ivanka', 'phone': 00321 }, ] }
781 }
782
783 U1db.Index {
784@@ -61,6 +61,13 @@
785 expression: ['gents.name', 'gents.phone']
786 }
787
788+ U1db.Index {
789+ id: byDate
790+ database: gents
791+ name: 'by-date'
792+ expression: ['date', 'sports', 'software']
793+ }
794+
795 U1db.Query {
796 id: defaultPhone
797 index: byPhone
798@@ -120,6 +127,12 @@
799 query: [{ 'name': 'Ivanka', 'phone': '*' }]
800 }
801
802+ U1db.Query {
803+ id: toplevelQuery
804+ index: byDate
805+ query: [{ 'date': '2014*', 'sports': 'basketball', 'software': 'linux' }]
806+ }
807+
808 SignalSpy {
809 id: spyDocumentsChanged
810 target: defaultPhone
811@@ -130,6 +143,30 @@
812 name: "U1dbDatabase"
813 when: windowShown
814
815+ function prettyJson(j) {
816+ var A = JSON.stringify(j)
817+ if (A['0'] && A != '{}') {
818+ var A = '['
819+ for(var i in j)
820+ A += JSON.stringify(j[i]) + ','
821+ A = A.substring(0, A.lastIndexOf(',')) + ']'
822+ }
823+ return A
824+ }
825+
826+ function compare (a, b) {
827+ /* Override built-in compare to:
828+ Match different JSON for identical values (number hash versus list)
829+ Produce readable output for all JSON values
830+ */
831+ if (a == b)
832+ return
833+ var A = prettyJson(a), B = prettyJson(b)
834+ if (A != B) {
835+ fail('%1 != %2 (%3 != %4)'.arg(A).arg(B).arg(JSON.stringify(a)).arg(JSON.stringify(b)))
836+ }
837+ }
838+
839 function workaroundQueryAndWait (buggyQuery) {
840 var realQuery = buggyQuery.query;
841 spyDocumentsChanged.target = buggyQuery
842@@ -177,6 +214,7 @@
843 workaroundQueryAndWait(ivankaAllNamePhoneKeywords)
844 workaroundQueryAndWait(ivankaAllNamePhone)
845 // FIXME: compare(ivankaAllNamePhone.documents, ivankaAllNamePhoneKeywords.documents, 'tres')
846+ compare(toplevelQuery.documents, ['_'])
847 }
848
849 function test_4_delete () {

Subscribers

People subscribed via source and target branches

to all changes: