summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDenis Dzyubenko <denis.dzyubenko@nokia.com>2012-03-28 11:43:09 +0200
committerDenis Dzyubenko <denis.dzyubenko@nokia.com>2012-03-28 11:43:12 +0200
commit74c9cdcc15f4c323bd766b1f769bd2248a0b582d (patch)
treee62f38a035c379f5d8ce3df5577efccb4afdef87
parent6db3585569eadd9aaaaa7871ca7a41b03e4d947d (diff)
parent897d1fad7c6ebb0c42e2c089dff89d980d9fa7df (diff)
Merge remote-tracking branch 'gerrit/master' into hbtree
-rw-r--r--doc/doc.pri3
-rw-r--r--doc/src/index.qdoc131
-rw-r--r--doc/src/map-views.qdoc24
-rw-r--r--doc/src/notifications.qdoc88
-rw-r--r--doc/src/object-indexes.qdoc18
-rw-r--r--doc/src/partitions.qdoc58
-rw-r--r--doc/src/queries.qdoc (renamed from doc/src/expression-examples.qdoc)44
-rw-r--r--doc/src/reduce-example.qdoc35
-rw-r--r--doc/src/reduce-views.qdoc136
-rw-r--r--src/client/qjsondbconnection.cpp30
-rw-r--r--src/client/qjsondbconnection_p.h2
-rw-r--r--src/client/qjsondbreadrequest.cpp4
-rw-r--r--src/client/qjsondbwatcher.h2
-rw-r--r--src/common/jsondbcollator.cpp26
-rw-r--r--src/common/jsondbcollator_p.h1
-rw-r--r--src/daemon/dbserver.cpp77
-rw-r--r--src/daemon/dbserver.h2
-rw-r--r--src/daemon/jsondbephemeralpartition.cpp8
-rw-r--r--src/daemon/jsondbindex.cpp13
-rw-r--r--src/daemon/jsondbindex.h7
-rw-r--r--src/daemon/jsondbmapdefinition.cpp182
-rw-r--r--src/daemon/jsondbmapdefinition.h6
-rw-r--r--src/daemon/jsondbobject.cpp48
-rw-r--r--src/daemon/jsondbobject.h1
-rw-r--r--src/daemon/jsondbobjecttable.cpp11
-rw-r--r--src/daemon/jsondbobjecttable.h5
-rw-r--r--src/daemon/jsondbpartition.cpp161
-rw-r--r--src/daemon/jsondbpartition.h10
-rw-r--r--src/daemon/jsondbquery.cpp70
-rw-r--r--src/daemon/jsondbquery.h1
-rw-r--r--src/daemon/jsondbreducedefinition.cpp175
-rw-r--r--src/daemon/jsondbreducedefinition.h19
-rw-r--r--src/daemon/jsondbview.cpp33
-rw-r--r--src/daemon/jsondbview.h3
-rw-r--r--src/daemon/schema/Index.json1
-rw-r--r--src/imports/jsondb-listmodel/jsondb-listmodel.pro12
-rw-r--r--src/imports/jsondb/jsondb.pro12
-rw-r--r--src/imports/jsondb/jsondbqueryobject.cpp10
-rw-r--r--tests/auto/accesscontrol/json/capabilities-view.json24
-rw-r--r--tests/auto/accesscontrol/json/view-test.json54
-rw-r--r--tests/auto/accesscontrol/json/view-test2.json38
-rw-r--r--tests/auto/accesscontrol/testjsondb.cpp47
-rw-r--r--tests/auto/client/test-jsondb-client.cpp87
-rw-r--r--tests/auto/daemon/json/map-reduce-schema.json1
-rw-r--r--tests/auto/daemon/json/map-reduce.json4
-rw-r--r--tests/auto/daemon/json/reduce.json4
-rw-r--r--tests/auto/daemon/testjsondb.cpp194
-rw-r--r--tests/auto/jsondbqueryobject/testjsondbqueryobject.cpp50
-rw-r--r--tests/auto/jsondbqueryobject/testjsondbqueryobject.h3
-rw-r--r--tests/auto/qjsondbwatcher/testqjsondbwatcher.cpp42
-rw-r--r--tests/auto/queries/dataset.json17
-rw-r--r--tests/auto/queries/testjsondbqueries.cpp57
-rw-r--r--tests/shared/util.h8
-rw-r--r--tools/aodbread/aodbread.pro11
-rw-r--r--tools/aodbread/main.cpp83
-rw-r--r--tools/jsondb-client/client.cpp63
-rw-r--r--tools/jsondb-client/jsondbproxy.cpp20
-rw-r--r--tools/jsondb-client/main.cpp8
58 files changed, 1774 insertions, 510 deletions
diff --git a/doc/doc.pri b/doc/doc.pri
index 7f7eff6..939ef9d 100644
--- a/doc/doc.pri
+++ b/doc/doc.pri
@@ -1,6 +1,7 @@
OTHER_FILES += \
$$PWD/jsondb.qdocconf \
- $$PWD/jsondb-dita.qdocconf
+ $$PWD/jsondb-dita.qdocconf \
+ $$PWD/src/*.qdoc
docs_target.target = docs
docs_target.commands = qdoc $$PWD/jsondb.qdocconf
diff --git a/doc/src/index.qdoc b/doc/src/index.qdoc
index 4026e8c..45de13f 100644
--- a/doc/src/index.qdoc
+++ b/doc/src/index.qdoc
@@ -26,11 +26,17 @@
****************************************************************************/
/*!
-\title Qt JsonDb Reference
+\title Qt JSON DB Reference
\page index.html
\section1 Introduction
+JSON DB is a no-SQL object store for Qt. It allows JSON objects to be stored,
+modified and queried. It does not require schemas like SQL database,
+which makes it particularly flexible. It has a number of useful features such
+as the ability to mash-up data with database views, push notifications for
+data changes in the database, and easy integration with QML-based UIs.
+
\section1 Accessing the Database.
\section2 C++ API
@@ -43,87 +49,73 @@
\section1 Using the Database
-\section2 Schemas
-
-\section2 Query Language
-
-Central to the use of a large object store is the ability to query efficiently
-for objects from within the object store. The JsonDB query language was
-originally modeled off of the JSON Query language, see
-\l {http://docs.persvr.org/documentation/jsonquery}. It has subsequently been
-updated for faster performance and features have been added that make it more
-suitable for our application.
-
-A simple query consists of square brackets surrounding an expression
-(e.g., [EXPR]). A complex query is a string of simple queries concatenated
-together (e.g., [EXPR1][EXPR2][EXPR3]). Each expression filters or applies an
-action.
-
-\section3 Valid Expressions
-
-\table
-\header
-\li Expression
-\li Description
-\row
-\li \c {[? filter]}
-\li Query for matching objects.
-\row
-\li \c {[= fields]}
-\li Restrict returned objects to particular fields.
-\row
-\li \c {[/ field]}
-\li Sort in ascending order by field.
-\row
-\li \c {[\ field]}
-\li Sort in descending order by field.
-\row
-\li \c {[count]}
-\li Aggregation operation.
-\row
-\li \c {[*]}
-\li Match all objects.
-\endtable
-
-See also \l {Expression Examples}
-
-\section3 Stability of Sort in JsonDb
-
-The database as a set of objects with no natural ordering. Any time that we need
-objects in a particular order, we use an index that orders the objects by a
-particular field and comparison operator. It is unlikely that the sorting in one
-index would be stable with respect to all other indexes on that object.
+\section2 Use of JSON
+
+The database operates on objects in JSON format. Any JSON object can be stored
+in the database, though JSON DB reserves properties beginning with an underscore
+for its own use.
+
+See \l {Special JSON Properties}
+
+\section2 Creating, Updating and Removing Objects
+
+Objects are manipulated in the database from C++ using the
+\l{QJsonDbWriteRequest} class and its associated convenience subclasses.
+
+In QML, objects are manipulated via the \l{Partition} item.
+
+\section2 Querying Objects
+
+Objects are queried from C++ using the \l{QJsonDbReadRequest} class and in QML
+using \l{Query} item.
+
+See \l {Queries}
+
+\section2 Partitions
+
+The database is sub-divided into a number of smaller partitions, each with its
+own unique name. All operations in JSON DB operate on exactly one partition at
+a time.
+
+See \l {Partitions}
\section2 Indexes
+The index facility is provided to make queries perform better and to allow the
+database to sort the returned data. Indexes are created in JSON DB by creating
+objects with the type \c Index.
+
See \l {Object Indexes}
-\section3 Query optimization
+\section2 Views
-In general, sorting should only be done on properties for which JSON DB has an
-index. Any query with a sort order given for which there is no index, will
-result in an error. For a query without explicitly provided sort order, full
-object scan is performed, which should be avoided for all but the smallest data
-sets.
+Views allow objects in JSON DB to be transformed and mashed-up into new objects.
+Views are defined via functions written in JavaScript and are automatically
+kept up to date by the database. When the source object of a view is modified
+in the database, JSON DB ensures the views that depend on that object are updated.
+Views in JSON DB are based heavily on the Map/Reduce concept (see
+\l {http://www.mapreduce.org}).
-\section2 Views
+Map functions are registered by creating an object of type \c Map in the database.
-\list
-\li \l {Map and Join Views}
-\endlist
+See \l {Map and Join Views}
+
+Reduce functions are registered by creating an object of type \c Reduce in the
+database.
+
+See \l {Reduce Views}
\section2 Notifications
-Notifications are created, updated, and removed by using a
-\l{QJsonDbConnection::addWatcher()}{dedicated api} which
-takes a query expression and a list of actions as an argument. There are
-actions available that allow watching for objects being created, updated or
-removed from a database.
+JSON DB clients can register to be informed about changes in the database via
+notifications. Clients create notifications containing a query to monitor.
+When an object matching the query is modified, JSON DB sends a notification
+message to the client.
-When a notification matches an action performed on the database, the
-QJsonDbWatcher::notificationsAvailable() signal is emitted.
+Notifications are registered via the \l {QJsonDbWatcher} class in C++ and
+via the \l {Notification} item in QML.
-See also \l{QJsonDbWatcher}
+See \l {Notifications}
\section1 Examples
@@ -134,6 +126,7 @@ See also \l{QJsonDbWatcher}
\li \l{declarative/desktop-inspector}{Desktop Database Inspector Example}
\li \l{declarative/map-phone}{Creating a Map View}
\li \l{declarative/joinview}{Creating a Join View}
+\li \l{declarative/reduce-phone}{Creating a Reduce View}
\li \l{client/chat}{Creating a chat client using the C++ API}
\endlist
*/
diff --git a/doc/src/map-views.qdoc b/doc/src/map-views.qdoc
index fcddae1..391c9c9 100644
--- a/doc/src/map-views.qdoc
+++ b/doc/src/map-views.qdoc
@@ -47,10 +47,32 @@ values of a single target type. The target type must extend \l View.
\li targetType
\li The output type of this mapping
\row
+\li targetKeyName
+\li Name of the key for the emitted objects. Optional. \sa deterministic-map-uuids
+\row
\li map
\li A dictionary whose keys are source type names and whose values are the functions to run on those source type.
\endtable
+\section2 Deterministic Map Uuids
+\target{deterministic-map-uuids}
+
+By default, View objects created by Map are assigned a deterministic Uuid. If
+the "map" or "join" functions assign _uuid to the object they
+return, then that value is used as the uuid for the object. Otherwise,
+the view engine constructs an identifier string as follows:
+\code
+ sourceUuids.sort();
+ if (targeKeyName.isEmpty()) {
+ var identifier = targetType + ":" + sourceUuids.join(":");
+ object._uuid = jsondb.createUuidFromString(identifier);
+ } else {
+ var keyValue = object[targetKeyName];
+ var identifier = targetType + ":" + sourceUuids.join(":") + ":" + keyValue;
+ object._uuid = jsondb.createUuidFromString(identifier);
+ }
+\endcode
+
\section2 Map Proxy
When the map functions run, they have access to a jsondb proxy object with one method:
@@ -87,7 +109,7 @@ A join is defined by creating a \l Map object in the database with a "join" prop
\li A dictionary whose keys are source type names and whose values are the functions to run on those source type.
\endtable
-\section2 JsonDb Join Proxy
+\section2 JSON DB Join Proxy
When the \l Join functions run, they have access to a jsondb proxy object with two methods:
diff --git a/doc/src/notifications.qdoc b/doc/src/notifications.qdoc
new file mode 100644
index 0000000..36a709f
--- /dev/null
+++ b/doc/src/notifications.qdoc
@@ -0,0 +1,88 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the documentation of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:FDL$
+** GNU Free Documentation License
+** Alternatively, this file may be used under the terms of the GNU Free
+** Documentation License version 1.3 as published by the Free Software
+** Foundation and appearing in the file included in the packaging of
+** this file.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms
+** and conditions contained in a signed written agreement between you
+** and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+/*!
+\page notifications.html
+\title Notifications
+
+JSON DB clients can register to be informed about changes in the database via
+notifications. Clients create notifications containing a query to monitor.
+When an object matching the query is modified, JSON DB sends a notification
+message to the client.
+
+Notifications are registered via the \l {QJsonDbWatcher} class in C++ and
+via the \l {Notification} item in QML.
+
+Notifications usually pertain to new events in the database, but by using the
+\l {QJsonDbWatcher::initialStateNumber} property, it's possible to request
+notifications based on past events in the database. This can be useful in
+writing a program that would need to synchronize with the database.
+
+\section1 Actions
+
+Clients may register to receive notifications based on one or more types
+of actions that may occur in the database. The available actions are:
+
+\table
+\row
+\row
+\li Created
+\li Notifications with this action generally represent entirely new objects
+which match the specified query being created in the database. Another case is an
+existing object which previously did not match the query being modified to now
+match the query.
+
+\row
+\li Updated
+\li Notifications with this action represent an object that matches the
+specified query being updated (while still matching the query).
+
+\row
+\li Removed
+\li Notifications with this action generally represent an existing object which
+matches the specified query being removed from the database. Another case is an
+existing object which previously matched the query being modified so that it no
+longer matches the query. Whether the object has been removed or not can be
+tested by checking if the object in the notification has a \c _deleted property
+with the value of \c true.
+
+\row
+\li StateChanged
+\li Used for historical notifications only. When this action is received, it
+means that the last notification for a particular state (transaction) has been
+received.
+
+\endtable
+
+\section1 Lifecycle
+
+Under-the-hood, both \l {QJsonDbWatcher} and \l {Notification} function by
+creating an object of type \c notification in the Ephemeral partition (see
+\l {Partitions}). Once the object is created, notifications can begin being
+sent. When a connection to the database drops, all of the notifications for
+that connection are removed.
+*/
diff --git a/doc/src/object-indexes.qdoc b/doc/src/object-indexes.qdoc
index ecf92f7..80c3aff 100644
--- a/doc/src/object-indexes.qdoc
+++ b/doc/src/object-indexes.qdoc
@@ -52,6 +52,15 @@ specified, name defaults to propertyName. If propertyFunction is
specified, then name must be specified.
\row
+\li objectType
+\li An optional string or array of strings, naming the object types to
+be indexed. If not specified, all objects in the main object table of
+a partition will be indexed.
+
+If specified for non-view types, the index should have a name that is
+distinct from its propertyName.
+
+\row
\li propertyName
\li A string or array of strings, naming the properties to be
indexed. Mutually exclusive with propertyFunction.
@@ -99,7 +108,14 @@ and "PreferLowerCase".
\endtable
-\section2 JsonDb Proxy
+\section1 Stability of Sort in JSON DB
+
+The database is a set of objects with no natural ordering. Any time that we need
+objects in a particular order, we use an index that orders the objects by a
+particular field and comparison operator. It is unlikely that the sorting in one
+index will be stable with respect to other indexes on that object.
+
+\section1 jsondb Proxy
For indexes that use a JavaScript function, when the propertyFunction
runs it has access to a client interface to the database. This proxy
diff --git a/doc/src/partitions.qdoc b/doc/src/partitions.qdoc
new file mode 100644
index 0000000..64911c2
--- /dev/null
+++ b/doc/src/partitions.qdoc
@@ -0,0 +1,58 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the documentation of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:FDL$
+** GNU Free Documentation License
+** Alternatively, this file may be used under the terms of the GNU Free
+** Documentation License version 1.3 as published by the Free Software
+** Foundation and appearing in the file included in the packaging of
+** this file.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms
+** and conditions contained in a signed written agreement between you
+** and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+/*!
+\page partitions.html
+\title Partitions
+
+\section1 Introduction
+
+JSON DB may be configured as a number of partitions, each with its
+own unique name. All operations in JSON DB operate on exactly one partition at
+a time.
+
+\section1 Accessing Partitions
+
+The various partitions in a set are accessed via the \l {Partition} element in QML. In
+C++, the partition is selected by \c setPartition() on a
+\l {QJsonDbReadRequest}, \l {QJsonDbWriteRequest} or \l {QJsonDbWatcher}.
+
+\section1 The Ephemeral Partition
+
+JSON DB offers a partition called \c Ephemeral. Objects written to this
+partition are not persistent, but rather stored entirely in memory. The
+entire partition is cleared every time the database server is restarted. This
+partition can be very useful for storing frequently updated data since it does not
+cause any disk I/O.
+
+\code
+QJsonDbReadRequest *request = new QJsonDbReadRequest(this);
+request->setQuery("[?_type=\"Status\"]");
+request->setPartition("Ephemeral");
+\endcode
+
+*/
diff --git a/doc/src/expression-examples.qdoc b/doc/src/queries.qdoc
index 15c8ba6..b34bdc0 100644
--- a/doc/src/expression-examples.qdoc
+++ b/doc/src/queries.qdoc
@@ -26,13 +26,28 @@
****************************************************************************/
/*!
-\page expression-examples.html
-\title Expression Examples
+\page queries.html
+\title Queries
-\previouspage {index.html}
+\section1 Basics
+
+Central to the use of a large object store is the ability to query efficiently
+for objects from within the object store. The JsonDB query language was
+originally modeled on of the JSON Query language (see
+\l {http://docs.persvr.org/documentation/jsonquery}). It has subsequently been
+updated for faster performance and features have been added that make it more
+suitable for our application.
+
+A simple query consists of square brackets surrounding an expression
+(e.g., [EXPR]). A complex query is a string of simple queries concatenated
+together (e.g., [EXPR1][EXPR2][EXPR3]). Each expression filters or applies an
+action.
+
+\section1 Valid Expressions
\table
\row
+\row
\li \c {[?_type="Person"]}
\li Return all objects that are Person types.
\row
@@ -42,6 +57,13 @@
\li \c {[?name exists][/_type]}
\li All objects with a "name" field in the database, sorted by _type.
\row
+\li \c {[?name notExists][/_type]}
+\li All objects not containing a "name" field in the database, sorted by _type.
+
+Note that this constraint precludes the use of an index on the "name"
+field, because only objects containing a "name" property will appear
+in the index on "name".
+\row
\li \c {[?name = %thename ][/_type]}
\li All objects from the database where the "name" field is the same as the
binding for thename in the bindings object provided to find, sorted by _type.
@@ -122,5 +144,21 @@ insensitive.
\li \c {[?_type="MESSAGE"][= { subject: Subject, sent: DateTimeSent, sender: Sender.Mailbox.EmailAddress } ]}
\li List of new objects containing of subject, sent time, and sender email
address from message objects.
+\row
+\li \c {[?_type="MESSAGE"][count]}
+\li Count the number of objects of type "MESSAGE"]
\endtable
+
+\section1 Query optimization
+
+In general, sorting can only be done on properties for which JSON DB has an
+index. Any query with a sort order given for which there is no index, will
+result in an error. For a query without explicitly provided sort order, a full
+object scan is performed. This should be avoided for all but the smallest data
+sets.
+
+See \l {Object Indexes}
+
*/
+
+
diff --git a/doc/src/reduce-example.qdoc b/doc/src/reduce-example.qdoc
new file mode 100644
index 0000000..a0a8b30
--- /dev/null
+++ b/doc/src/reduce-example.qdoc
@@ -0,0 +1,35 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the documentation of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:FDL$
+** GNU Free Documentation License
+** Alternatively, this file may be used under the terms of the GNU Free
+** Documentation License version 1.3 as published by the Free Software
+** Foundation and appearing in the file included in the packaging of
+** this file.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms
+** and conditions contained in a signed written agreement between you
+** and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+/*!
+
+\page creating-a-reduce.html
+\title Creating a Reduce View
+
+\example declarative/reduce-phone
+
+*/
diff --git a/doc/src/reduce-views.qdoc b/doc/src/reduce-views.qdoc
new file mode 100644
index 0000000..d16c5f9
--- /dev/null
+++ b/doc/src/reduce-views.qdoc
@@ -0,0 +1,136 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the documentation of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:FDL$
+** GNU Free Documentation License
+** Alternatively, this file may be used under the terms of the GNU Free
+** Documentation License version 1.3 as published by the Free Software
+** Foundation and appearing in the file included in the packaging of
+** this file.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms
+** and conditions contained in a signed written agreement between you
+** and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+/*!
+\page reduce-views.html
+\title Reduce Views
+
+\target reduce
+
+\section1 Performing a Reduce
+
+A reduce is defined by creating a \l Reduce object in the database. A
+Reduce operates on objects of a single source type, producing values
+of a single target type. A Reduce incrementally combines source
+objects with matching keys using JavaScript "add" and "subtract"
+functions.
+
+\table
+\row
+\li _type
+\li Reduce
+
+\row
+\li targetType
+\li The output type of this reduction. The target type must extend \l View.
+
+\row
+\li targetKeyName
+\li The name of the key property in the target objects. Defaults to "key" if unspecified.
+
+\row
+\li targetValueName (should be "null")
+
+\li The name of the value property in the target objects. Defaults to
+"value" if unspecified. If this is defined to be "null", then the
+object returned from the "add" and "subtract" functions is used as the
+whole target object. The old behavior is deprecated and will be
+removed.
+
+\row
+\li sourceType
+\li The type of the source objects used in this reduction
+
+\row
+\li sourceKeyName
+
+\li The name of the key property in the source objects. Defaults to
+"key" if unspecified. Exactly one of sourceKeyName and
+sourceKeyFunction must be specified.
+
+\row
+\li sourceKeyFunction
+
+\li A string that represents a JavaScript function that takes an
+object and returns the key value for that object. Exactly one of
+sourceKeyName and sourceKeyFunction must be specified.
+
+\row
+\li add
+\li A string that evaluates to a Javascript function taking three arguments: keyValue, targetObject, sourceObject..
+
+\row
+\li subtract
+\li A string that evaluates to a Javascript function taking three arguments: keyValue, targetObject, sourceObject.
+\endtable
+
+
+\section2 Deterministic Uuids
+
+View objects created by Reduce are assigned a deterministic Uuid. If
+the "add" or "subtract" functions assign _uuid to the object they
+return, then that value is used as the uuid for the object. Otherwise,
+the view engine constructs an identifier string as follows:
+\code
+ var identifier = targetType + ":" + reduceDefinitionUuid + ":" + keyValue;
+ var uuid = jsondb.createUuidFromString(identifier);
+\endcode
+
+\section2 Theory of Operation
+
+When the view is updated, it operates on all of the source objects
+that were changed since the last time the view was updated.
+
+If a source object is created, the key is extracted from the object
+using either sourceKeyName or sourceKeyFunction. The "add" function is
+applied to three arguments: the key value, the previous target object
+for the key value or undefined, and the source object.
+
+If a source object is removed, the key is extracted from the object
+that was removed using either sourceKeyName or sourceKeyFunction. The
+"remove" function is applied to three arguments: the key value, the
+previous target object for the key value or undefined, and the source
+object.
+
+If a source object is updated, it is treated as a remove of the
+previous value and a creation of the new value, so there will be a
+call to the "subtract" function and then the "add" function.
+
+All of the changes are staged together during an update of the view to
+minimize the number of changes of target objects visible via
+notifications.
+
+\section2 Reduce Proxy
+
+When the map functions run, they have access to a jsondb proxy object with one method:
+
+\table
+\row
+\li \l {jsondb.createUuidFromString}{jsondb.createUuidFromString}(identifier)
+\endtable
+
+*/
diff --git a/src/client/qjsondbconnection.cpp b/src/client/qjsondbconnection.cpp
index c2253fb..f7f10b1 100644
--- a/src/client/qjsondbconnection.cpp
+++ b/src/client/qjsondbconnection.cpp
@@ -49,6 +49,7 @@
#include <qcoreevent.h>
#include <qtimer.h>
#include <qjsonarray.h>
+#include <qthreadstorage.h>
/*!
\macro QT_USE_NAMESPACE_JSONDB
@@ -100,8 +101,7 @@
QT_BEGIN_NAMESPACE_JSONDB
-QJsonDbConnection *QJsonDbConnectionPrivate::defaultConnection = 0;
-Q_GLOBAL_STATIC(QJsonDbConnection, _q_defaultConnection)
+Q_GLOBAL_STATIC(QThreadStorage<QJsonDbConnection *>, _q_defaultConnection);
/*!
\class QJsonDbConnection
@@ -702,31 +702,33 @@ bool QJsonDbConnection::removeWatcher(QJsonDbWatcher *watcher)
/*!
\nonreentrant
- Sets the global default jsondb connection to \a connection.
-
- \warning In a multithreaded application, the default connection should be
- set on application startup, before any threads that use jsondb are created.
+ Sets the default jsondb connection for the current thread to \a connection.
+ This transfers ownership of \a connection to QtJsonDb, it will be deleted
+ on thread exit, or on the next call to setDefaultConnection().
\sa QJsonDbConnection::defaultConnection()
*/
void QJsonDbConnection::setDefaultConnection(QJsonDbConnection *connection)
{
- QJsonDbConnectionPrivate::defaultConnection = connection;
+ _q_defaultConnection()->setLocalData(connection);
}
/*!
- Returns the default jsondb connection object.
-
- \warning This function is thread-safe, however the QJsonDbConnection object
- is not.
+ Returns the default jsondb connection object for the current thread. If no
+ connection has been set for the current thread with setDefaultConnection(),
+ a new connection is created.
\sa QJsonDbConnection::setDefaultConnection()
*/
QJsonDbConnection *QJsonDbConnection::defaultConnection()
{
- if (QJsonDbConnectionPrivate::defaultConnection)
- return QJsonDbConnectionPrivate::defaultConnection;
- return _q_defaultConnection();
+ QThreadStorage<QJsonDbConnection *> *storage = _q_defaultConnection();
+ QJsonDbConnection *defaultConnection = storage->localData();
+ if (!defaultConnection) {
+ defaultConnection = new QJsonDbConnection;
+ storage->setLocalData(defaultConnection);
+ }
+ return defaultConnection;
}
/*!
diff --git a/src/client/qjsondbconnection_p.h b/src/client/qjsondbconnection_p.h
index 9ed5349..bf65e6b 100644
--- a/src/client/qjsondbconnection_p.h
+++ b/src/client/qjsondbconnection_p.h
@@ -95,8 +95,6 @@ public:
void removeWatcher(QJsonDbWatcher *);
void reactivateAllWatchers();
- static QJsonDbConnection *defaultConnection;
-
QJsonDbConnection *q_ptr;
QString socketName;
QJsonDbConnection::Status status;
diff --git a/src/client/qjsondbreadrequest.cpp b/src/client/qjsondbreadrequest.cpp
index 181b4f8..dae0774 100644
--- a/src/client/qjsondbreadrequest.cpp
+++ b/src/client/qjsondbreadrequest.cpp
@@ -54,7 +54,7 @@ QT_BEGIN_NAMESPACE_JSONDB
\brief The QJsonDbReadRequest class allows to query database.
- See \l{Expression Examples} for documentation on the query string format.
+ See \l{Queries} for documentation on the query string format.
\code
QJsonDbReadRequest *request = new QJsonDbReadRequest;
@@ -130,7 +130,7 @@ QJsonDbReadRequest::~QJsonDbReadRequest()
\brief the query string
Specifies the query string for the request in the format described in
- \l{Expression Examples}.
+ \l{Queries}.
\sa queryLimit, bindValue()
*/
diff --git a/src/client/qjsondbwatcher.h b/src/client/qjsondbwatcher.h
index c2101ed..d647fdf 100644
--- a/src/client/qjsondbwatcher.h
+++ b/src/client/qjsondbwatcher.h
@@ -115,7 +115,7 @@ public:
Q_SIGNALS:
void notificationsAvailable(int count);
void statusChanged(QtJsonDb::QJsonDbWatcher::Status newStatus);
- void error(int code, const QString &message);
+ void error(QtJsonDb::QJsonDbWatcher::ErrorCode code, const QString &message);
// signals for properties
void lastStateNumberChanged(int stateNumber);
diff --git a/src/common/jsondbcollator.cpp b/src/common/jsondbcollator.cpp
index 19726fe..4fa45c4 100644
--- a/src/common/jsondbcollator.cpp
+++ b/src/common/jsondbcollator.cpp
@@ -52,6 +52,12 @@
QT_BEGIN_NAMESPACE_JSONDB
+JsonDbCollatorPrivate::~JsonDbCollatorPrivate()
+{
+ if (collator)
+ ucol_close(collator);
+}
+
static const int collationStringsCount = 13;
static const char * const collationStrings[collationStringsCount] = {
"default",
@@ -87,19 +93,15 @@ JsonDbCollator::JsonDbCollator(const JsonDbCollator &other)
JsonDbCollator::~JsonDbCollator()
{
- if (!d->ref.deref()) {
- if (d->collator)
- ucol_close(d->collator);
- }
+ if (!d->ref.deref())
+ delete d;
}
JsonDbCollator &JsonDbCollator::operator=(const JsonDbCollator &other)
{
if (this != &other) {
- if (!d->ref.deref()) {
- if (d->collator)
- ucol_close(d->collator);
- }
+ if (!d->ref.deref())
+ delete d;
*d = *other.d;
d->ref.ref();
}
@@ -146,7 +148,7 @@ void JsonDbCollator::init()
Q_ASSERT((int)d->collation < collationStringsCount);
const char *collationString = collationStrings[(int)d->collation];
UErrorCode status = U_ZERO_ERROR;
- QByteArray name = (d->locale.bcp47Name().replace(QLatin1Char('-'), QLatin1Char('_')) + QString("@collation=") + QLatin1String(collationString)).toLatin1();
+ QByteArray name = (d->locale.bcp47Name().replace(QLatin1Char('-'), QLatin1Char('_')) + QLatin1String("@collation=") + QLatin1String(collationString)).toLatin1();
d->collator = ucol_open(name.constData(), &status);
if (U_FAILURE(status))
qWarning("Could not create collator: %d", status);
@@ -211,10 +213,8 @@ void JsonDbCollator::detach()
x->options = d->options;
x->modified = true;
x->collator = 0;
- if (!d->ref.deref()) {
- if (d->collator)
- ucol_close(d->collator);
- }
+ if (!d->ref.deref())
+ delete d;
d = x;
}
}
diff --git a/src/common/jsondbcollator_p.h b/src/common/jsondbcollator_p.h
index 3902369..5a76f29 100644
--- a/src/common/jsondbcollator_p.h
+++ b/src/common/jsondbcollator_p.h
@@ -73,6 +73,7 @@ public:
options(0),
collator(0)
{ ref.store(1); }
+ ~JsonDbCollatorPrivate();
int compare(ushort *s1, int len1, ushort *s2, int len2);
};
diff --git a/src/daemon/dbserver.cpp b/src/daemon/dbserver.cpp
index d7cc336..29b050c 100644
--- a/src/daemon/dbserver.cpp
+++ b/src/daemon/dbserver.cpp
@@ -103,6 +103,9 @@ DBServer::DBServer(const QString &filePath, const QString &baseName, QObject *pa
mFilePath(filePath),
mBaseName(baseName)
{
+ // for queued connection handling
+ qRegisterMetaType<JsonDbPartition*>("JsonDbPartition*");
+ qRegisterMetaType<QSet<QString> >("QSet<QString>");
QFileInfo info(filePath);
@@ -229,18 +232,24 @@ bool DBServer::loadPartitions()
this, SLOT(objectsUpdated(JsonDbUpdateList)));
}
+ QHash<QString, JsonDbPartition*> oldPartitions = mPartitions;
+ oldPartitions.remove(mBaseName);
if (!mDefaultPartition) {
mDefaultPartition = new JsonDbPartition(QDir(mFilePath).absoluteFilePath(mBaseName + QLatin1String(".db")),
mBaseName, mOwner, this);
connect(mDefaultPartition, SIGNAL(objectsUpdated(JsonDbUpdateList)),
this, SLOT(objectsUpdated(JsonDbUpdateList)));
+ connect(mDefaultPartition, SIGNAL(viewUpdated(QString)),
+ this, SLOT(viewUpdated(QString)),
+ Qt::QueuedConnection);
if (!mDefaultPartition->open())
return false;
+
+ mPartitions[mBaseName] = mDefaultPartition;
}
- QHash<QString, JsonDbPartition*> oldPartitions = mPartitions;
JsonDbQueryResult partitions = mDefaultPartition->queryObjects(mOwner, JsonDbQuery::parse(QLatin1String("[?_type=\"Partition\"]")));
foreach (const JsonDbObject &partition, partitions.data) {
@@ -254,6 +263,9 @@ bool DBServer::loadPartitions()
JsonDbPartition *p = new JsonDbPartition(filename, name, mOwner, this);
connect(p, SIGNAL(objectsUpdated(JsonDbUpdateList)),
this, SLOT(objectsUpdated(JsonDbUpdateList)));
+ connect(p, SIGNAL(viewUpdated(QString)),
+ this, SLOT(viewUpdated(QString)),
+ Qt::QueuedConnection);
if (!p->open())
return false;
@@ -430,6 +442,8 @@ void DBServer::objectsUpdated(const QList<JsonDbUpdate> &objects)
return;
}
+ QSet<QString> eagerViewUpdates;
+
// FIXME: pretty good place to batch notifications
foreach (const JsonDbUpdate &updated, objects) {
@@ -452,14 +466,13 @@ void DBServer::objectsUpdated(const QList<JsonDbUpdate> &objects)
QStringList notificationKeys;
if (object.contains(JsonDbString::kTypeStr)) {
notificationKeys << objectType;
- if (mEagerViewSourceTypes.contains(objectType)) {
- const QSet<QString> &targetTypes = mEagerViewSourceTypes[objectType];
- for (QSet<QString>::const_iterator it = targetTypes.begin(); it != targetTypes.end(); ++it) {
- if (partition)
- partition->updateView(*it);
- }
- }
+
+ // eagerly update views if this object that was created isn't a view type itself
+ if (mEagerViewSourceTypes.contains(objectType) && partition
+ && !partition->findView(objectType))
+ eagerViewUpdates.insert(objectType);
}
+
if (object.contains(JsonDbString::kUuidStr))
notificationKeys << object.value(JsonDbString::kUuidStr).toString();
notificationKeys << "__generic_notification__";
@@ -477,6 +490,32 @@ void DBServer::objectsUpdated(const QList<JsonDbUpdate> &objects)
}
}
}
+
+ if (!eagerViewUpdates.isEmpty())
+ QMetaObject::invokeMethod(this, "updateEagerViews", Qt::QueuedConnection,
+ Q_ARG(JsonDbPartition*, partition),
+ Q_ARG(QSet<QString>, eagerViewUpdates));
+}
+
+void DBServer::viewUpdated(const QString &type)
+{
+ JsonDbPartition *partition = qobject_cast<JsonDbPartition*>(sender());
+ if (!partition)
+ return;
+
+ if (mEagerViewSourceTypes.contains(type))
+ updateEagerViews(partition, QSet<QString>() << type);
+}
+
+void DBServer::updateEagerViews(JsonDbPartition *partition, const QSet<QString> &viewTypes)
+{
+ foreach (const QString &type, viewTypes) {
+ const QSet<QString> &targetTypes = mEagerViewSourceTypes[type];
+ for (QSet<QString>::const_iterator it = targetTypes.begin(); it != targetTypes.end(); ++it) {
+ if (partition)
+ partition->updateView(*it);
+ }
+ }
}
void DBServer::objectUpdated(const QString &partitionName, quint32 stateNumber, JsonDbNotification *n,
@@ -498,7 +537,8 @@ void DBServer::objectUpdated(const QString &partitionName, quint32 stateNumber,
effectiveAction = JsonDbNotification::Create;
} else if (oldMatches && (!newMatches || object.isDeleted())) {
r = oldObject;
- r.insert(JsonDbString::kDeletedStr, true);
+ if (object.isDeleted())
+ r.insert(JsonDbString::kDeletedStr, true);
effectiveAction = JsonDbNotification::Delete;
} else if (oldMatches && newMatches) {
effectiveAction = JsonDbNotification::Update;
@@ -584,9 +624,7 @@ void DBServer::processRead(JsonStream *stream, JsonDbOwner *owner, const QJsonVa
int limit = request.contains(JsonDbString::kLimitStr) ? request.value(JsonDbString::kLimitStr).toDouble() : -1;
int offset = request.value(JsonDbString::kOffsetStr).toDouble();
- if (!parsedQuery->queryTerms.size() && !parsedQuery->orderTerms.size())
- response = createError(JsonDbError::MissingQuery, QString("Missing query: ") + parsedQuery->queryExplanation.join("\n"));
- else if (limit < -1)
+ if (limit < -1)
response = createError(JsonDbError::InvalidLimit, "Invalid limit");
else if (offset < 0)
response = createError(JsonDbError::InvalidOffset, "Invalid offset");
@@ -760,7 +798,12 @@ void DBServer::createNotification(const JsonDbObject &object, JsonStream *stream
QString query = object.value(JsonDbString::kQueryStr).toString();
QJsonObject bindings = object.value("bindings").toObject();
QString partition = object.value(JsonDbString::kPartitionStr).toString();
- quint32 stateNumber = object.value("initialStateNumber").toDouble();
+
+ bool ok;
+ quint32 stateNumber = object.value("initialStateNumber").toVariant().toInt(&ok);
+ if (!ok)
+ stateNumber = 0;
+
if (partition.isEmpty())
partition = mDefaultPartition->name();
@@ -851,6 +894,11 @@ void DBServer::notifyHistoricalChanges(JsonDbNotification *n)
}
foreach (const QString matchedType, matchedTypes) {
JsonDbObjectTable *objectTable = partition->findObjectTable(matchedType);
+
+ // views dont have a _type index
+ if (partition->findView(matchedType))
+ indexName = JsonDbString::kUuidStr;
+
lastStateNumber = objectTable->stateNumber();
JsonDbIndexQuery *indexQuery = JsonDbIndexQuery::indexQuery(partition, objectTable,
indexName, QString("string"),
@@ -907,8 +955,7 @@ void DBServer::updateEagerViewTypes(const QString &objectType, JsonDbPartition *
JsonDbPartition *DBServer::findPartition(const QString &partitionName)
{
JsonDbPartition *partition = mDefaultPartition;
- // default partition is not in mPartitions
- if (!partitionName.isEmpty() && partitionName != mDefaultPartition->name()) {
+ if (!partitionName.isEmpty()) {
if (mPartitions.contains(partitionName))
partition = mPartitions[partitionName];
else
diff --git a/src/daemon/dbserver.h b/src/daemon/dbserver.h
index 3cadf38..d4df0cc 100644
--- a/src/daemon/dbserver.h
+++ b/src/daemon/dbserver.h
@@ -92,6 +92,8 @@ protected slots:
void notified(const QString &id, quint32 stateNumber, const QJsonObject &object, const QString &action);
void objectsUpdated(const JsonDbUpdateList &objects);
+ void viewUpdated(const QString &type);
+ void updateEagerViews(JsonDbPartition *partition, const QSet<QString> &viewTypes);
private:
void objectUpdated(const QString &partitionName, quint32 stateNumber, JsonDbNotification *n, JsonDbNotification::Action action, const JsonDbObject &oldObject, const JsonDbObject &object);
diff --git a/src/daemon/jsondbephemeralpartition.cpp b/src/daemon/jsondbephemeralpartition.cpp
index dee0983..989748f 100644
--- a/src/daemon/jsondbephemeralpartition.cpp
+++ b/src/daemon/jsondbephemeralpartition.cpp
@@ -129,6 +129,14 @@ JsonDbWriteResult JsonDbEphemeralPartition::updateObjects(const JsonDbOwner *own
if (!mObjects.contains(object.uuid()))
action = JsonDbNotification::Create;
+ // TODO: maybe be this somewhere else so that it doesn't pollute this code
+ if (object.type() == JsonDbString::kNotificationTypeStr &&
+ object.value(JsonDbString::kQueryStr).toString().isEmpty()) {
+ result.code = JsonDbError::MissingQuery;
+ result.message = QLatin1String("Notification objects must contain a query paramater");
+ return result;
+ }
+
object.insert(JsonDbString::kOwnerStr, owner->ownerId());
object.computeVersion();
mObjects[object.uuid()] = object;
diff --git a/src/daemon/jsondbindex.cpp b/src/daemon/jsondbindex.cpp
index 90ce8fa..356e3f8 100644
--- a/src/daemon/jsondbindex.cpp
+++ b/src/daemon/jsondbindex.cpp
@@ -100,10 +100,12 @@ JsonDbCollator::CasePreference _q_correctCasePreferenceString(const QString &s)
#else
int _q_correctCollationString(const QString &s)
{
+ Q_UNUSED(s);
return 0;
}
int _q_correctCasePreferenceString(const QString &s)
{
+ Q_UNUSED(s);
return 0;
}
#endif //NO_COLLATION_SUPPORT
@@ -130,13 +132,15 @@ QString _q_bytesToHexString(const QByteArray &ba)
}
JsonDbIndex::JsonDbIndex(const QString &fileName, const QString &indexName, const QString &propertyName,
- const QString &propertyType, const QString &locale, const QString &collation,
+ const QString &propertyType, const QStringList &objectType, const QString &locale, const QString &collation,
const QString &casePreference, Qt::CaseSensitivity caseSensitivity, JsonDbObjectTable *objectTable)
: QObject(objectTable)
, mObjectTable(objectTable)
+ , mIndexName(indexName)
, mPropertyName(propertyName)
, mPath(propertyName.split('.'))
, mPropertyType(propertyType)
+ , mObjectType(objectType)
, mLocale(locale)
, mCollation(collation)
, mCasePreference(casePreference)
@@ -239,7 +243,7 @@ bool JsonDbIndex::validateIndex(const JsonDbObject &newIndex, const JsonDbObject
message = QString("Changing old index propertyType from '%1' to '%2' not supported")
.arg(oldIndex.value(JsonDbString::kPropertyTypeStr).toString())
.arg(newIndex.value(JsonDbString::kPropertyTypeStr).toString());
- else if (oldIndex.value(JsonDbString::kObjectTypeStr).toString() != newIndex.value(JsonDbString::kObjectTypeStr).toString())
+ else if (oldIndex.value(JsonDbString::kObjectTypeStr) != newIndex.value(JsonDbString::kObjectTypeStr))
message = QString("Changing old index objectType from '%1' to '%2' not supported")
.arg(oldIndex.value(JsonDbString::kObjectTypeStr).toString())
.arg(newIndex.value(JsonDbString::kObjectTypeStr).toString());
@@ -330,6 +334,9 @@ void JsonDbIndex::indexObject(const ObjectKey &objectKey, JsonDbObject &object,
if (mPropertyName == JsonDbString::kUuidStr)
return;
+ if (!mObjectType.isEmpty() && !mObjectType.contains(object.value(JsonDbString::kTypeStr).toString()))
+ return;
+
Q_ASSERT(!object.contains(JsonDbString::kDeletedStr)
&& !object.value(JsonDbString::kDeletedStr).toBool());
QList<QJsonValue> fieldValues = indexValues(object);
@@ -370,6 +377,8 @@ void JsonDbIndex::deindexObject(const ObjectKey &objectKey, JsonDbObject &object
{
if (mPropertyName == JsonDbString::kUuidStr)
return;
+ if (!mObjectType.isEmpty() && !mObjectType.contains(object.value(JsonDbString::kTypeStr).toString()))
+ return;
if (!mBdb)
open();
QList<QJsonValue> fieldValues = indexValues(object);
diff --git a/src/daemon/jsondbindex.h b/src/daemon/jsondbindex.h
index 97520fa..dfbd084 100644
--- a/src/daemon/jsondbindex.h
+++ b/src/daemon/jsondbindex.h
@@ -71,7 +71,7 @@ class JsonDbIndex : public QObject
Q_OBJECT
public:
JsonDbIndex(const QString &fileName, const QString &indexName, const QString &propertyName,
- const QString &propertyType, const QString &locale, const QString &collation,
+ const QString &propertyType, const QStringList &objectType, const QString &locale, const QString &collation,
const QString &casePreference, Qt::CaseSensitivity caseSensitivity,
JsonDbObjectTable *objectTable);
~JsonDbIndex();
@@ -79,6 +79,7 @@ public:
QString propertyName() const { return mPropertyName; }
QStringList fieldPath() const { return mPath; }
QString propertyType() const { return mPropertyType; }
+ QStringList objectType() const { return mObjectType; }
QtAddOn::JsonDb::JsonDbBtree *bdb();
@@ -112,9 +113,11 @@ private slots:
private:
JsonDbObjectTable *mObjectTable;
QString mFileName;
+ QString mIndexName;
QString mPropertyName;
QStringList mPath;
QString mPropertyType;
+ QStringList mObjectType;
QString mLocale;
QString mCollation;
QString mCasePreference;
@@ -165,7 +168,7 @@ public:
QString collation;
QString casePreference;
Qt::CaseSensitivity caseSensitivity;
- QString objectType;
+ QStringList objectType;
bool lazy;
QPointer<JsonDbIndex> index;
};
diff --git a/src/daemon/jsondbmapdefinition.cpp b/src/daemon/jsondbmapdefinition.cpp
index a3fe0d1..23baa64 100644
--- a/src/daemon/jsondbmapdefinition.cpp
+++ b/src/daemon/jsondbmapdefinition.cpp
@@ -73,28 +73,22 @@ JsonDbMapDefinition::JsonDbMapDefinition(const JsonDbOwner *owner, JsonDbPartiti
, mTargetType(definition.value("targetType").toString())
, mTargetTable(mPartition->findObjectTable(mTargetType))
{
- if (!mDefinition.contains("sourceType")) {
- QJsonObject sourceFunctions(mDefinition.contains("join")
- ? mDefinition.value("join").toObject()
- : mDefinition.value("map").toObject());
- mSourceTypes = sourceFunctions.keys();
- for (int i = 0; i < mSourceTypes.size(); i++) {
- const QString &sourceType = mSourceTypes[i];
- mSourceTables[sourceType] = mPartition->findObjectTable(sourceType);
- }
- } else {
- // TODO: remove this case
- const QString sourceType = mDefinition.value("sourceType").toString();
+ QJsonObject sourceFunctions(mDefinition.contains("join")
+ ? mDefinition.value("join").toObject()
+ : mDefinition.value("map").toObject());
+ mSourceTypes = sourceFunctions.keys();
+ for (int i = 0; i < mSourceTypes.size(); i++) {
+ const QString &sourceType = mSourceTypes[i];
mSourceTables[sourceType] = mPartition->findObjectTable(sourceType);
- mSourceTypes.append(sourceType);
}
+ if (mDefinition.contains("targetKeyName"))
+ mTargetKeyName = mDefinition.value("targetKeyName").toString();
}
void JsonDbMapDefinition::definitionCreated()
{
- initScriptEngine();
-
- mTargetTable->addIndexOnProperty(QLatin1String("_sourceUuids.*"), QLatin1String("string"), mTargetType);
+ initScriptEngine();
+ initIndexes();
foreach (const QString &sourceType, mSourceTypes) {
GetObjectsResult getObjectResponse = mPartition->getObjects(JsonDbString::kTypeStr, sourceType);
@@ -135,54 +129,46 @@ void JsonDbMapDefinition::initScriptEngine()
return;
mScriptEngine = new QJSEngine(this);
+ QString message;
+ bool compiled = compileMapFunctions(mScriptEngine, mDefinition, mMapFunctions, message);
+ if (!compiled)
+ setError(message);
QJSValue globalObject = mScriptEngine->globalObject();
globalObject.setProperty("console", mScriptEngine->newQObject(new Console()));
- if (!mDefinition.contains("sourceType")) {
- QJsonObject sourceFunctions(mDefinition.contains("join")
- ? mDefinition.value("join").toObject()
- : mDefinition.value("map").toObject());
- for (int i = 0; i < mSourceTypes.size(); i++) {
- const QString &sourceType = mSourceTypes[i];
- const QString &script = sourceFunctions.value(sourceType).toString();
- QJSValue mapFunction =
- mScriptEngine->evaluate(QString("var map_%1 = (%2); map_%1;").arg(QString(sourceType).replace(".", "_")).arg(script));
- if (mapFunction.isError() || !mapFunction.isCallable())
- setError( "Unable to parse map function: " + mapFunction.toString());
- mMapFunctions[sourceType] = mapFunction;
- }
-
- mJoinProxy = new JsonDbJoinProxy(mOwner, mPartition, this);
- connect(mJoinProxy, SIGNAL(lookupRequested(QJSValue,QJSValue)),
- this, SLOT(lookupRequested(QJSValue,QJSValue)));
- connect(mJoinProxy, SIGNAL(viewObjectEmitted(QJSValue)),
- this, SLOT(viewObjectEmitted(QJSValue)));
- globalObject.setProperty("_jsondb", mScriptEngine->newQObject(mJoinProxy));
- // we use this snippet of javascript so that we can bind "jsondb.emit"
- // even though "emit" is a Qt keyword
- if (mDefinition.contains("join"))
- // only joins can use lookup()
- mScriptEngine->evaluate("var jsondb = { emit: _jsondb.create, lookup: _jsondb.lookup, createUuidFromString: _jsondb.createUuidFromString};");
- else
- mScriptEngine->evaluate("var jsondb = { emit: _jsondb.create, createUuidFromString: _jsondb.createUuidFromString };");
+ mJoinProxy = new JsonDbJoinProxy(mOwner, mPartition, this);
+ connect(mJoinProxy, SIGNAL(lookupRequested(QJSValue,QJSValue)),
+ this, SLOT(lookupRequested(QJSValue,QJSValue)));
+ connect(mJoinProxy, SIGNAL(viewObjectEmitted(QJSValue)),
+ this, SLOT(viewObjectEmitted(QJSValue)));
+ globalObject.setProperty("_jsondb", mScriptEngine->newQObject(mJoinProxy));
+ // we use this snippet of javascript so that we can bind "jsondb.emit"
+ // even though "emit" is a Qt keyword
+ if (mDefinition.contains("join"))
+ // only joins can use lookup()
+ mScriptEngine->evaluate("var jsondb = { emit: _jsondb.create, lookup: _jsondb.lookup, createUuidFromString: _jsondb.createUuidFromString};");
+ else
+ mScriptEngine->evaluate("var jsondb = { emit: _jsondb.create, createUuidFromString: _jsondb.createUuidFromString };");
+}
- } else {
- const QString sourceType = mDefinition.value("sourceType").toString();
- const QString &script = mDefinition.value("map").toString();
+bool JsonDbMapDefinition::compileMapFunctions(QJSEngine *scriptEngine, QJsonObject definition, QMap<QString,QJSValue> &mapFunctions, QString &message)
+{
+ bool status = true;
+ QJsonObject sourceFunctions(definition.contains("join")
+ ? definition.value("join").toObject()
+ : definition.value("map").toObject());
+ for (QJsonObject::const_iterator it = sourceFunctions.begin(); it != sourceFunctions.end(); ++it) {
+ const QString &sourceType = it.key();
+ const QString &script = it.value().toString();
QJSValue mapFunction =
- mScriptEngine->evaluate(QString("var map_%1 = (%2); map_%1;").arg(QString(sourceType).replace(".", "_")).arg(script));
- if (mapFunction.isError() || !mapFunction.isCallable())
- setError( "Unable to parse map function: " + mapFunction.toString());
- mMapFunctions[sourceType] = mapFunction;
-
- mMapProxy = new JsonDbMapProxy(mOwner, mPartition, this);
- connect(mMapProxy, SIGNAL(lookupRequested(QJSValue,QJSValue)),
- this, SLOT(lookupRequested(QJSValue,QJSValue)));
- connect(mMapProxy, SIGNAL(viewObjectEmitted(QJSValue)),
- this, SLOT(viewObjectEmitted(QJSValue)));
- globalObject.setProperty("jsondb", mScriptEngine->newQObject(mMapProxy));
- qWarning() << "Old-style Map from sourceType" << sourceType << " to targetType" << mDefinition.value("targetType");
+ scriptEngine->evaluate(QString("var map_%1 = (%2); map_%1;").arg(QString(sourceType).replace(".", "_")).arg(script));
+ if (mapFunction.isError() || !mapFunction.isCallable()) {
+ message = QString( "Unable to parse map function: " + mapFunction.toString());
+ status = false;
+ }
+ mapFunctions[sourceType] = mapFunction;
}
+ return status;
}
void JsonDbMapDefinition::releaseScriptEngine()
@@ -192,6 +178,12 @@ void JsonDbMapDefinition::releaseScriptEngine()
mScriptEngine = 0;
}
+
+void JsonDbMapDefinition::initIndexes()
+{
+ mTargetTable->addIndexOnProperty(QLatin1String("_sourceUuids.*"), QLatin1String("string"), mTargetType);
+}
+
void JsonDbMapDefinition::updateObject(const JsonDbObject &beforeObject, const JsonDbObject &afterObject)
{
QHash<QString, JsonDbObject> unmappedObjects;
@@ -338,7 +330,7 @@ void JsonDbMapDefinition::lookupRequested(const QJSValue &query, const QJSValue
if (mapped.isError())
setError("Error executing map function during lookup: " + mapped.toString());
- mSourceUuids.removeLast();
+ mSourceUuids.removeOne(uuid);
}
}
@@ -346,13 +338,32 @@ void JsonDbMapDefinition::viewObjectEmitted(const QJSValue &value)
{
JsonDbObject newItem(JsonDbObject::fromJSValue(value).toObject());
newItem.insert(JsonDbString::kTypeStr, mTargetType);
- QJsonArray sourceUuids;
- foreach (const QString &str, mSourceUuids)
- sourceUuids.append(str);
- newItem.insert("_sourceUuids", sourceUuids);
-
- if (!newItem.contains(JsonDbString::kUuidStr))
- newItem.generateUuid();
+ mSourceUuids.sort();
+ QJsonArray sourceUuidArray;
+ foreach (const QString &sourceUuid, mSourceUuids)
+ sourceUuidArray.append(sourceUuid);
+ newItem.insert("_sourceUuids", sourceUuidArray);
+
+ if (!newItem.contains(JsonDbString::kUuidStr)) {
+ if (newItem.contains(QLatin1String("_id")))
+ newItem.generateUuid();
+ else {
+ QString targetKeyString;
+ if (!mTargetKeyName.isEmpty())
+ targetKeyString = JsonDbObject(newItem).propertyLookup(mTargetKeyName).toString();
+
+ // colon separated sorted source uuids
+ QString sourceUuidString = mSourceUuids.join(":");
+ QString identifier =
+ QString("%1:%2%3%4")
+ .arg(mTargetType)
+ .arg(sourceUuidString)
+ .arg(targetKeyString.isEmpty() ? "" : ":")
+ .arg(targetKeyString);
+ newItem.insert(JsonDbString::kUuidStr,
+ JsonDbObject::createUuidFromString(identifier).toString());
+ }
+ }
QString uuid = newItem.value(JsonDbString::kUuidStr).toString();
mEmittedObjects.insert(uuid, newItem);
@@ -381,14 +392,18 @@ bool JsonDbMapDefinition::validateDefinition(const JsonDbObject &map, JsonDbPart
if (targetType.isEmpty()) {
message = QLatin1Literal("targetType property for Map not specified");
+ } else if (map.contains(QLatin1Literal("sourceType"))) {
+ message = QLatin1Literal("sourceType property for Map no longer supported");
} else if (!view) {
message = QLatin1Literal("targetType must be of a type that extends View");
} else if (map.contains("join")) {
QJsonObject sourceFunctions = map.value("join").toObject();
- sourceTypes = sourceFunctions.keys();
+
if (sourceFunctions.isEmpty())
message = QLatin1Literal("sourceTypes and functions for Map with join not specified");
+ sourceTypes = sourceFunctions.keys();
+
foreach (const QString &sourceType, sourceTypes) {
if (sourceFunctions.value(sourceType).toString().isEmpty())
message = QString("join function for source type '%1' not specified for Map").arg(sourceType);
@@ -401,14 +416,41 @@ bool JsonDbMapDefinition::validateDefinition(const JsonDbObject &map, JsonDbPart
if (map.contains("map"))
message = QLatin1Literal("Map 'join' and 'map' options are mutually exclusive");
- else if (map.contains("sourceType"))
- message = QLatin1Literal("Map 'join' and 'sourceType' options are mutually exclusive");
} else {
QJsonValue mapValue = map.value("map");
- if (map.value("sourceType").toString().isEmpty() && !mapValue.isObject())
+ if (!mapValue.isObject())
message = QLatin1String("sourceType property for Map not specified");
else if (!mapValue.isString() && !mapValue.isObject())
message = QLatin1String("map function for Map not specified");
+
+ if (mapValue.isObject()) {
+
+ QJsonObject sourceFunctions = mapValue.toObject();
+
+ if (sourceFunctions.isEmpty())
+ message = QLatin1Literal("sourceTypes and functions for Map with map not specified");
+
+ sourceTypes = sourceFunctions.keys();
+
+ foreach (const QString &sourceType, sourceTypes) {
+ if (sourceFunctions.value(sourceType).toString().isEmpty())
+ message = QString("map function for source type '%1' not specified for Map").arg(sourceType);
+ if (view->mMapDefinitionsBySource.contains(sourceType)
+ && view->mMapDefinitionsBySource.value(sourceType)->uuid() != uuid)
+ message =
+ QString("duplicate Map definition on source %1 and target %2")
+ .arg(sourceType).arg(targetType);
+ }
+ }
+ }
+
+ // check for parse errors
+ if (message.isEmpty()) {
+ // FIXME: This is static because otherwise we leak memory per QJSEngine instance
+ static QJSEngine *scriptEngine = new QJSEngine;
+ QMap<QString,QJSValue> mapFunctions;
+ compileMapFunctions(scriptEngine, map, mapFunctions, message);
+ scriptEngine->collectGarbage();
}
return message.isEmpty();
diff --git a/src/daemon/jsondbmapdefinition.h b/src/daemon/jsondbmapdefinition.h
index 6e78b21..61b19b3 100644
--- a/src/daemon/jsondbmapdefinition.h
+++ b/src/daemon/jsondbmapdefinition.h
@@ -83,9 +83,12 @@ public:
void initScriptEngine();
void releaseScriptEngine();
+ void initIndexes();
+
void setError(const QString &errorMsg);
void updateObject(const JsonDbObject &before, const JsonDbObject &after);
static bool validateDefinition(const JsonDbObject &map, JsonDbPartition *partition, QString &message);
+ static bool compileMapFunctions(QJSEngine *scriptEngine, QJsonObject definition, QMap<QString,QJSValue> &mapFunctions, QString &message);
public slots:
void viewObjectEmitted(const QJSValue &value);
@@ -99,6 +102,7 @@ private:
JsonDbPartition *mPartition;
const JsonDbOwner *mOwner;
QJsonObject mDefinition;
+ QString mTargetKeyName;
QJSEngine *mScriptEngine;
JsonDbMapProxy *mMapProxy; // to be removed when old map/lookup converted to join/lookup
JsonDbJoinProxy *mJoinProxy;
@@ -108,7 +112,7 @@ private:
QStringList mSourceTypes;
JsonDbObjectTable *mTargetTable;
QMap<QString,JsonDbObjectTable *> mSourceTables;
- QList<QString> mSourceUuids;
+ QStringList mSourceUuids; // a set of uuids with sorted elements
QHash<QString,JsonDbObject> mEmittedObjects;
};
diff --git a/src/daemon/jsondbobject.cpp b/src/daemon/jsondbobject.cpp
index 07cbabd..1a7467b 100644
--- a/src/daemon/jsondbobject.cpp
+++ b/src/daemon/jsondbobject.cpp
@@ -53,23 +53,6 @@
QT_ADDON_JSONDB_BEGIN_NAMESPACE
-static QUuid generateUUIDv3(const QString &uri)
-{
- QCryptographicHash hash(QCryptographicHash::Md5);
- hash.addData(QUuid(0x6ba7b810, 0x9dad, 0x11d1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8).toRfc4122());
- hash.addData(uri.toUtf8());
- QByteArray hashResult = hash.result();
-
- QUuid result = QUuid::fromRfc4122(hashResult);
-
- result.data3 &= 0x0FFF;
- result.data3 |= (3 << 12);
- result.data4[0] &= 0x3F;
- result.data4[0] |= 0x80;
-
- return result;
-}
-
JsonDbObject::JsonDbObject()
{
}
@@ -118,12 +101,39 @@ void JsonDbObject::markDeleted()
insert(JsonDbString::kDeletedStr, true);
}
+struct Uuid
+{
+ uint data1;
+ ushort data2;
+ ushort data3;
+ uchar data4[8];
+};
+
+// copied from src/client/qjsondbobject.cpp:
+static const Uuid JsonDbNamespace = {0x6ba7b810, 0x9dad, 0x11d1, { 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} };
+
+/*!
+ Returns deterministic uuid that can be used to identify given \a identifier.
+
+ The uuid is generated using QtJsonDb UUID namespace on a value of the
+ given \a identifier.
+
+ \sa QJsonDbObject::createUuidFromString(), QJsonDbObject::createUuid()
+*/
+QUuid JsonDbObject::createUuidFromString(const QString &identifier)
+{
+ const QUuid ns(JsonDbNamespace.data1, JsonDbNamespace.data2, JsonDbNamespace.data3,
+ JsonDbNamespace.data4[0], JsonDbNamespace.data4[1], JsonDbNamespace.data4[2],
+ JsonDbNamespace.data4[3], JsonDbNamespace.data4[4], JsonDbNamespace.data4[5],
+ JsonDbNamespace.data4[6], JsonDbNamespace.data4[7]);
+ return QUuid::createUuidV3(ns, identifier);
+}
+
void JsonDbObject::generateUuid()
{
QLatin1String idStr("_id");
if (contains(idStr)) {
- QByteArray rfc4122 = generateUUIDv3(value(idStr).toString()).toRfc4122();
- QUuid uuid(QUuid::fromRfc4122((rfc4122)));
+ QUuid uuid(createUuidFromString(value(idStr).toString()));
insert(JsonDbString::kUuidStr, uuid.toString());
} else {
QUuid uuid(QUuid::createUuid());
diff --git a/src/daemon/jsondbobject.h b/src/daemon/jsondbobject.h
index 29e704a..b648518 100644
--- a/src/daemon/jsondbobject.h
+++ b/src/daemon/jsondbobject.h
@@ -73,6 +73,7 @@ public:
void markDeleted();
void generateUuid();
+ static QUuid createUuidFromString(const QString &id);
QString computeVersion();
bool updateVersionOptimistic(const JsonDbObject &other, QString *versionWritten);
diff --git a/src/daemon/jsondbobjecttable.cpp b/src/daemon/jsondbobjecttable.cpp
index 807afff..36daede 100644
--- a/src/daemon/jsondbobjecttable.cpp
+++ b/src/daemon/jsondbobjecttable.cpp
@@ -249,7 +249,7 @@ QHash<QString, IndexSpec> JsonDbObjectTable::indexSpecs() const
}
bool JsonDbObjectTable::addIndex(const QString &indexName, const QString &propertyName,
- const QString &propertyType, const QString &objectType, const QString &propertyFunction,
+ const QString &propertyType, const QStringList &objectTypes, const QString &propertyFunction,
const QString &locale, const QString &collation, const QString &casePreference,
Qt::CaseSensitivity caseSensitivity)
{
@@ -273,9 +273,9 @@ bool JsonDbObjectTable::addIndex(const QString &indexName, const QString &proper
indexSpec.collation = collation;
indexSpec.casePreference = casePreference;
indexSpec.caseSensitivity = caseSensitivity;
- indexSpec.objectType = objectType;
+ indexSpec.objectType = objectTypes;
indexSpec.lazy = false; //lazy;
- indexSpec.index = new JsonDbIndex(mFilename, name, propertyName, propertyType, locale, collation, casePreference, caseSensitivity, this);
+ indexSpec.index = new JsonDbIndex(mFilename, name, propertyName, propertyType, objectTypes, locale, collation, casePreference, caseSensitivity, this);
if (!propertyFunction.isEmpty() && propertyName.isEmpty()) // propertyName takes precedence
indexSpec.index->setPropertyFunction(propertyFunction);
indexSpec.index->setCacheSize(jsondbSettings->cacheSize());
@@ -291,7 +291,10 @@ bool JsonDbObjectTable::addIndex(const QString &indexName, const QString &proper
indexObject.insert(JsonDbString::kCollationStr, collation);
indexObject.insert(JsonDbString::kCaseSensitiveStr, (bool)caseSensitivity);
indexObject.insert(JsonDbString::kCasePreferenceStr, casePreference);
- indexObject.insert(JsonDbString::kObjectTypeStr, objectType);
+ QJsonArray objectTypeList;
+ foreach (const QString objectType, objectTypes)
+ objectTypeList.append(objectType);
+ indexObject.insert(JsonDbString::kObjectTypeStr, objectTypeList);
indexObject.insert("lazy", false);
indexObject.insert(JsonDbString::kPropertyFunctionStr, propertyFunction);
Q_ASSERT(!name.isEmpty());
diff --git a/src/daemon/jsondbobjecttable.h b/src/daemon/jsondbobjecttable.h
index e02194e..61d0fc0 100644
--- a/src/daemon/jsondbobjecttable.h
+++ b/src/daemon/jsondbobjecttable.h
@@ -140,7 +140,7 @@ public:
bool addIndex(const QString &indexName,
const QString &propertyName = QString(),
const QString &propertyType = QString("string"),
- const QString &objectType = QString(),
+ const QStringList &objectTypes = QStringList(),
const QString &propertyFunction = QString(),
const QString &locale = QString(),
const QString &collation = QString(),
@@ -149,7 +149,8 @@ public:
bool addIndexOnProperty(const QString &propertyName,
const QString &propertyType = QString("string"),
const QString &objectType = QString())
- { return addIndex(propertyName, propertyName, propertyType, objectType); }
+ { return addIndex(propertyName, propertyName, propertyType,
+ objectType.isEmpty() ? QStringList() : (QStringList() << objectType)); }
bool removeIndex(const QString &indexName);
void reindexObjects(const QString &indexName, const QStringList &path, quint32 stateNumber);
void indexObject(const ObjectKey &objectKey, JsonDbObject object, quint32 stateNumber);
diff --git a/src/daemon/jsondbpartition.cpp b/src/daemon/jsondbpartition.cpp
index 73c5cb2..75dab7b 100644
--- a/src/daemon/jsondbpartition.cpp
+++ b/src/daemon/jsondbpartition.cpp
@@ -459,6 +459,7 @@ JsonDbView *JsonDbPartition::addView(const QString &viewType)
return view;
view = new JsonDbView(this, viewType, this);
+ connect(view, SIGNAL(updated(QString)), this, SIGNAL(viewUpdated(QString)));
view->open();
mViews.insert(viewType, view);
return view;
@@ -695,33 +696,52 @@ void JsonDbPartition::initIndexes()
QString indexName = JsonDbIndex::determineName(indexObject);
QString propertyName = indexObject.value(JsonDbString::kPropertyNameStr).toString();
QString propertyType = indexObject.value(JsonDbString::kPropertyTypeStr).toString();
- QString objectType = indexObject.value(JsonDbString::kObjectTypeStr).toString();
QString propertyFunction = indexObject.value(JsonDbString::kPropertyFunctionStr).toString();
QString locale = indexObject.value(JsonDbString::kLocaleStr).toString();
QString collation = indexObject.value(JsonDbString::kCollationStr).toString();
QString casePreference = indexObject.value(JsonDbString::kCasePreferenceStr).toString();
+ QStringList objectTypes;
+ QJsonValue objectTypeValue = indexObject.value(JsonDbString::kObjectTypeStr);
+ if (objectTypeValue.isString()) {
+ objectTypes.append(objectTypeValue.toString());
+ } else if (objectTypeValue.isArray()) {
+ foreach (const QJsonValue objectType, objectTypeValue.toArray())
+ objectTypes.append(objectType.toString());
+ }
+
Qt::CaseSensitivity caseSensitivity = Qt::CaseSensitive;
if (indexObject.contains(JsonDbString::kCaseSensitiveStr))
caseSensitivity = (indexObject.value(JsonDbString::kCaseSensitiveStr).toBool() == true ? Qt::CaseSensitive : Qt::CaseInsensitive);
- addIndex(indexName, propertyName, propertyType, objectType, propertyFunction, locale, collation, casePreference, caseSensitivity);
+ addIndex(indexName, propertyName, propertyType, objectTypes, propertyFunction, locale, collation, casePreference, caseSensitivity);
}
}
}
bool JsonDbPartition::addIndex(const QString &indexName, const QString &propertyName,
- const QString &propertyType, const QString &objectType, const QString &propertyFunction,
+ const QString &propertyType, const QStringList &objectTypes, const QString &propertyFunction,
const QString &locale, const QString &collation, const QString &casePreference,
Qt::CaseSensitivity caseSensitivity)
{
Q_ASSERT(!indexName.isEmpty());
//qDebug() << "JsonDbBtreePartition::addIndex" << propertyName << objectType;
- JsonDbObjectTable *table = findObjectTable(objectType);
+ JsonDbObjectTable *table = 0;
+ if (objectTypes.isEmpty())
+ table = mainObjectTable();
+ else
+ foreach (const QString &objectType, objectTypes) {
+ JsonDbObjectTable *t = findObjectTable(objectType);
+ if (table && (t != table)) {
+ qDebug() << "addIndex" << "index on multiple tables" << objectTypes;
+ return false;
+ }
+ table = t;
+ }
const IndexSpec *indexSpec = table->indexSpec(indexName);
if (indexSpec)
return true;
//if (gVerbose) qDebug() << "JsonDbBtreePartition::addIndex" << propertyName << objectType;
- return table->addIndex(indexName, propertyName, propertyType, objectType, propertyFunction, locale, collation, casePreference, caseSensitivity);
+ return table->addIndex(indexName, propertyName, propertyType, objectTypes, propertyFunction, locale, collation, casePreference, caseSensitivity);
}
bool JsonDbPartition::removeIndex(const QString &indexName, const QString &objectType)
@@ -853,7 +873,11 @@ JsonDbIndexQuery *JsonDbPartition::compileIndexQuery(const JsonDbOwner *owner, c
int indexedQueryTermCount = 0;
JsonDbObjectTable *table = mObjectTable; //TODO fix me
JsonDbView *view = 0;
+ QList<QString> unindexablePropertyNames; // fields for which we cannot use an index
if (orQueryTerms.size()) {
+ // first pass to find unindexable property names
+ for (int i = 0; i < orQueryTerms.size(); i++)
+ unindexablePropertyNames.append(orQueryTerms[i].findUnindexablePropertyNames());
for (int i = 0; i < orQueryTerms.size(); i++) {
const OrQueryTerm orQueryTerm = orQueryTerms[i];
const QList<QString> &querypropertyNames = orQueryTerm.propertyNames();
@@ -872,7 +896,9 @@ JsonDbIndexQuery *JsonDbPartition::compileIndexQuery(const JsonDbOwner *owner, c
if (table->indexSpec(propertyName))
indexedQueryTermCount++;
- else if (indexCandidate.isEmpty() && (propertyName != JsonDbString::kTypeStr)) {
+ else if (indexCandidate.isEmpty()
+ && (propertyName != JsonDbString::kTypeStr)
+ && !unindexablePropertyNames.contains(propertyName)) {
indexCandidate = propertyName;
if (!queryTerm.joinField().isEmpty())
indexCandidate = queryTerm.joinPaths()[0].join("->");
@@ -938,19 +964,14 @@ JsonDbIndexQuery *JsonDbPartition::compileIndexQuery(const JsonDbOwner *owner, c
if (!table->indexSpec(propertyName)) {
if (jsondbSettings->verbose() || jsondbSettings->performanceLog())
qDebug() << "Unindexed sort term" << propertyName << orderTerm.ascending;
- if (0) {
- if (jsondbSettings->verbose())
- qDebug() << "adding index for sort term" << propertyName;
- Q_ASSERT(table);
- //TODO: remove this
- table->addIndexOnProperty(propertyName);
- Q_ASSERT(table->indexSpec(propertyName));
- if (jsondbSettings->verbose())
- qDebug() << "done adding index" << propertyName;
- } else {
- residualQuery->orderTerms.append(orderTerm);
- continue;
- }
+ residualQuery->orderTerms.append(orderTerm);
+ continue;
+ }
+ if (unindexablePropertyNames.contains(propertyName)) {
+ if (jsondbSettings->verbose() || jsondbSettings->performanceLog())
+ qDebug() << "Unindexable sort term uses notExists" << propertyName << orderTerm.ascending;
+ residualQuery->orderTerms.append(orderTerm);
+ continue;
}
if (!indexQuery) {
orderField = propertyName;
@@ -992,7 +1013,10 @@ JsonDbIndexQuery *JsonDbPartition::compileIndexQuery(const JsonDbOwner *owner, c
continue;
}
- if (!indexQuery && (propertyName != JsonDbString::kTypeStr) && table->indexSpec(propertyName)) {
+ if (!indexQuery
+ && (propertyName != JsonDbString::kTypeStr)
+ && table->indexSpec(propertyName)
+ && !unindexablePropertyNames.contains(propertyName)) {
orderField = propertyName;
const IndexSpec *indexSpec = table->indexSpec(propertyName);
if (view)
@@ -1103,6 +1127,14 @@ JsonDbQueryResult JsonDbPartition::queryObjects(const JsonDbOwner *owner, const
JsonDbObjectList results;
JsonDbObjectList joinedResults;
+ if (!(query->queryTerms.size() || query->orderTerms.size())) {
+ QJsonObject error;
+ error.insert(JsonDbString::kCodeStr, JsonDbError::MissingQuery);
+ error.insert(JsonDbString::kMessageStr, QString("Missing query: %1").arg(query->queryExplanation.join("\n")));
+ result.error = error;
+ return result;
+ }
+
QElapsedTimer time;
time.start();
JsonDbIndexQuery *indexQuery = compileIndexQuery(owner, query);
@@ -1348,6 +1380,10 @@ JsonDbWriteResult JsonDbPartition::updateObjects(const JsonDbOwner *owner, const
result.code = JsonDbError::InvalidSchemaOperation;
result.message = errorMsg;
return result;
+ } else if ((errorCode = checkBuiltInTypeAccessControl(forCreation, owner, master, oldMaster, errorMsg)) != JsonDbError::NoError) {
+ result.code = errorCode;
+ result.message = errorMsg;
+ return result;
}
}
@@ -1551,6 +1587,70 @@ bool JsonDbPartition::checkNaturalObjectType(const JsonDbObject &object, QString
return true;
}
+JsonDbError::ErrorCode JsonDbPartition::checkBuiltInTypeAccessControl(bool forCreation, const JsonDbOwner *owner, const JsonDbObject &object, const JsonDbObject &oldObject, QString &errorMsg)
+{
+ QString objectType = object.value(JsonDbString::kTypeStr).toString();
+ errorMsg.clear();
+
+ // Access control checks
+ if (objectType == JsonDbString::kMapTypeStr ||
+ objectType == JsonDbString::kReduceTypeStr) {
+ // Check that owner can write targetType
+ QJsonValue targetType = object.value(QLatin1String("targetType"));
+ JsonDbObject fake; // Just for access control
+ fake.insert (JsonDbString::kOwnerStr, object.value(JsonDbString::kOwnerStr));
+ fake.insert (JsonDbString::kTypeStr, targetType);
+ if (!owner->isAllowed(fake, mPartitionName, "write")) {
+ errorMsg = QString::fromLatin1("Access denied %1").arg(targetType.toString());
+ return JsonDbError::OperationNotPermitted;
+ }
+ bool forRemoval = object.isDeleted();
+
+ // For removal it is enough to be able to write to targetType
+ if (!forRemoval) {
+ if (!forCreation) {
+ // In update we want to check also the old targetType
+ QJsonValue oldTargetType = oldObject.value(QLatin1String("targetType"));
+ fake.insert (JsonDbString::kTypeStr, oldTargetType);
+ if (!owner->isAllowed(fake, mPartitionName, "write")) {
+ errorMsg = QString::fromLatin1("Access denied %1").arg(oldTargetType.toString());
+ return JsonDbError::OperationNotPermitted;
+ }
+ }
+ // For create/update we need to check the read acces to sourceType(s) also
+ if (objectType == JsonDbString::kMapTypeStr) {
+ QScopedPointer<JsonDbMapDefinition> def(new JsonDbMapDefinition(owner, this, object));
+ QStringList sourceTypes = def->sourceTypes();
+ for (int i = 0; i < sourceTypes.size(); i++) {
+ fake.insert (JsonDbString::kTypeStr, sourceTypes[i]);
+ if (!owner->isAllowed(fake, mPartitionName, "read")) {
+ errorMsg = QString::fromLatin1("Access denied %1").arg(sourceTypes[i]);
+ return JsonDbError::OperationNotPermitted;
+ }
+ }
+ } else if (objectType == JsonDbString::kReduceTypeStr) {
+ QJsonValue sourceType = object.value(QLatin1String("sourceType"));
+ fake.insert (JsonDbString::kTypeStr, sourceType);
+ if (!owner->isAllowed(fake, mPartitionName, "read")) {
+ errorMsg = QString::fromLatin1("Access denied %1").arg(sourceType.toString());
+ return JsonDbError::OperationNotPermitted;
+ }
+ }
+ }
+ } else if (objectType == JsonDbString::kSchemaTypeStr) {
+ // Check that owner can write name
+ QJsonValue name = object.value(QLatin1String("name"));
+ JsonDbObject fake; // Just for access control
+ fake.insert (JsonDbString::kOwnerStr, object.value(JsonDbString::kOwnerStr));
+ fake.insert (JsonDbString::kTypeStr, name);
+ if (!owner->isAllowed(fake, mPartitionName, "write")) {
+ errorMsg = QString::fromLatin1("Access denied %1").arg(name.toString());
+ return JsonDbError::OperationNotPermitted;
+ }
+ }
+ return JsonDbError::NoError;
+}
+
JsonDbError::ErrorCode JsonDbPartition::checkBuiltInTypeValidity(const JsonDbObject &object, const JsonDbObject &oldObject, QString &errorMsg)
{
QString objectType = object.value(JsonDbString::kTypeStr).toString();
@@ -1585,10 +1685,20 @@ void JsonDbPartition::updateBuiltInTypes(const JsonDbObject &object, const JsonD
if (object.contains(JsonDbString::kCaseSensitiveStr))
caseSensitivity = object.value(JsonDbString::kCaseSensitiveStr).toBool();
+ QStringList objectTypes;
+ QJsonValue v = object.value(JsonDbString::kObjectTypeStr);
+ if (v.isString()) {
+ objectTypes = (QStringList() << v.toString());
+ } else if (v.isArray()) {
+ QJsonArray array = v.toArray();
+ foreach (const QJsonValue objectType, array)
+ objectTypes.append(objectType.toString());
+ }
+
addIndex(indexName,
object.value(JsonDbString::kPropertyNameStr).toString(),
object.value(JsonDbString::kPropertyTypeStr).toString(),
- object.value(JsonDbString::kObjectTypeStr).toString(),
+ objectTypes,
object.value(JsonDbString::kPropertyFunctionStr).toString(),
object.value(JsonDbString::kLocaleStr).toString(),
object.value(JsonDbString::kCollationStr).toString(),
@@ -1608,7 +1718,8 @@ void JsonDbPartition::updateBuiltInTypes(const JsonDbObject &object, const JsonD
if (!oldObject.isEmpty())
JsonDbView::removeDefinition(this, oldObject);
- if (!object.isDeleted())
+ if (!(object.isDeleted() ||
+ (object.contains(JsonDbString::kActiveStr) && !object.value(JsonDbString::kActiveStr).toBool())))
JsonDbView::createDefinition(this, object);
}
}
@@ -1681,7 +1792,8 @@ void JsonDbPartition::updateSchemaIndexes(const QString &schemaName, QJsonObject
QString propertyType = (propertyInfo.contains("type") ? propertyInfo.value("type").toString() : "string");
QStringList kpath = path;
kpath << k;
- addIndexOnProperty(kpath.join("."), propertyType, schemaName);
+ QString propertyName = kpath.join(".");
+ addIndex(propertyName, propertyName, propertyType);
}
if (propertyInfo.contains("properties"))
updateSchemaIndexes(schemaName, propertyInfo, path + (QStringList() << k));
@@ -1781,6 +1893,7 @@ void JsonDbPartition::initSchemas()
{
JsonDbObject nameIndex;
nameIndex.insert(JsonDbString::kTypeStr, JsonDbString::kIndexTypeStr);
+ nameIndex.insert(JsonDbString::kNameStr, QLatin1String("capabilityName"));
nameIndex.insert(JsonDbString::kPropertyNameStr, QLatin1String("name"));
nameIndex.insert(JsonDbString::kPropertyTypeStr, QLatin1String("string"));
nameIndex.insert(JsonDbString::kObjectTypeStr, QLatin1String("Capability"));
@@ -1797,7 +1910,7 @@ void JsonDbPartition::initSchemas()
}
JsonDbObject capability = QJsonObject::fromVariantMap(parser.result().toMap());
QString name = capability.value("name").toString();
- GetObjectsResult getObjectResponse = getObjects("name", name, "Capability");
+ GetObjectsResult getObjectResponse = getObjects("capabilityName", name, "Capability");
int count = getObjectResponse.data.size();
if (!count) {
if (jsondbSettings->verbose())
diff --git a/src/daemon/jsondbpartition.h b/src/daemon/jsondbpartition.h
index 7d62249..f56eb40 100644
--- a/src/daemon/jsondbpartition.h
+++ b/src/daemon/jsondbpartition.h
@@ -116,16 +116,12 @@ public:
bool addIndex(const QString &indexName,
const QString &propertyName,
const QString &propertyType = QString("string"),
- const QString &objectType = QString(),
+ const QStringList &objectTypes = QStringList(),
const QString &propertyFunction = QString(),
const QString &locale = QString(),
const QString &collation = QString(),
const QString &casePreference = QString(),
Qt::CaseSensitivity caseSensitive = Qt::CaseSensitive);
- bool addIndexOnProperty(const QString &propertyName,
- const QString &propertyType = QString("string"),
- const QString &objectType = QString())
- { return addIndex(propertyName, propertyName, propertyType, objectType); }
bool removeIndex(const QString &indexName, const QString &objectType = QString());
bool checkQuota(const JsonDbOwner *owner, int size) const;
@@ -173,6 +169,7 @@ public Q_SLOTS:
void updateView(const QString &objectType, quint32 stateNumber=0);
Q_SIGNALS:
+ void viewUpdated(const QString &objectType);
void objectsUpdated(const JsonDbUpdateList &objects);
protected:
@@ -197,13 +194,14 @@ protected:
bool checkNaturalObjectType(const JsonDbObject &object, QString &errorMsg);
JsonDbError::ErrorCode checkBuiltInTypeValidity(const JsonDbObject &object, const JsonDbObject &oldObject, QString &errorMsg);
+ JsonDbError::ErrorCode checkBuiltInTypeAccessControl(bool forCreation, const JsonDbOwner *owner, const JsonDbObject &object,
+ const JsonDbObject &oldObject, QString &errorMsg);
void updateBuiltInTypes(const JsonDbObject &object, const JsonDbObject &oldObject);
void setSchema(const QString &schemaName, const QJsonObject &schema);
void removeSchema(const QString &schemaName);
void updateSchemaIndexes(const QString &schemaName, QJsonObject object, const QStringList &path=QStringList());
private:
-
JsonDbObjectTable *mObjectTable;
QVector<JsonDbObjectTable *> mTableTransactions;
diff --git a/src/daemon/jsondbquery.cpp b/src/daemon/jsondbquery.cpp
index aa03fa9..be38e2c 100644
--- a/src/daemon/jsondbquery.cpp
+++ b/src/daemon/jsondbquery.cpp
@@ -41,6 +41,8 @@
#include <QDebug>
#include <QFile>
+#include <QJsonDocument>
+#include <QStack>
#include <QString>
#include <QStringList>
@@ -261,6 +263,9 @@ JsonDbQuery *JsonDbQuery::parse(const QString &query, const QJsonObject &binding
JsonDbQuery *parsedQuery = new JsonDbQuery;
parsedQuery->query = query;
+ if (!query.startsWith('['))
+ return parsedQuery;
+
bool parseError = false;
JsonDbQueryTokenizer tokenizer(query);
QString token;
@@ -338,6 +343,57 @@ JsonDbQuery *JsonDbQuery::parse(const QString &query, const QJsonObject &binding
term.regExp().setCaseSensitivity(Qt::CaseInsensitive);
//qDebug() << "pattern" << tvs.mid(2, eor-2);
term.regExp().setPattern(tvs.mid(2, eor-2));
+ } else if (op == "contains") {
+ bool ok = true;;
+
+ QString value = tokenizer.pop();
+ if (value == "[" || value == "{") {
+ QStack<QString> tokenStack;
+ tokenStack.push(value);
+ QString tkn = value;
+
+ while (ok && !tkn.isEmpty()) {
+ tkn = tokenizer.pop();
+ if (tkn == "]" && tokenStack.isEmpty()) {
+ tokenizer.push(tkn);
+ break;
+ } else {
+ value += tkn;
+ if (tkn == "]") {
+ if (tokenStack.pop() != "[")
+ ok = false;
+ } else if (tkn == "}") {
+ if (tokenStack.pop() != "{")
+ ok = false;
+ }
+ }
+ }
+
+ if (ok) {
+ QJsonParseError parserError;
+ QJsonDocument parsedValue = QJsonDocument::fromJson(value.toUtf8(), &parserError);
+ if (parserError.error != QJsonParseError::NoError) {
+ ok = false;
+ } else {
+ if (parsedValue.isArray())
+ term.setValue(parsedValue.array());
+ else
+ term.setValue(parsedValue.object());
+ }
+ }
+ } else {
+ parseJsonLiteral(value, &term, bindings, &ok);
+ }
+
+ if (!ok) {
+ parsedQuery->queryExplanation.append(QString("Failed to parse query value '%1' in query '%2' %3 op %4")
+ .arg(value)
+ .arg(parsedQuery->query)
+ .arg(fieldSpec)
+ .arg(op));
+ parseError = true;
+ break;
+ }
} else if ((op != "exists") && (op != "notExists")) {
QString value = tokenizer.pop();
bool ok = true;;
@@ -472,7 +528,7 @@ JsonDbQuery *JsonDbQuery::parse(const QString &query, const QJsonObject &binding
}
}
}
- // TODO look at this again
+
if (!parsedQuery->queryTerms.size() && !parsedQuery->orderTerms.size()) {
// match everything -- sort on type
OrderTerm term;
@@ -612,6 +668,18 @@ QList<QString> OrQueryTerm::propertyNames() const
return propertyNames;
}
+QList<QString> OrQueryTerm::findUnindexablePropertyNames() const
+{
+ QList<QString> unindexablePropertyNames;
+ foreach (const QueryTerm &term, mTerms) {
+ const QString propertyName = term.propertyName();
+ const QString op = term.op();
+ if (op == QLatin1String("notExists") && !unindexablePropertyNames.contains(propertyName))
+ unindexablePropertyNames.append(propertyName);
+ }
+ return unindexablePropertyNames;
+}
+
OrderTerm::OrderTerm()
{
}
diff --git a/src/daemon/jsondbquery.h b/src/daemon/jsondbquery.h
index 6c57f62..30b7501 100644
--- a/src/daemon/jsondbquery.h
+++ b/src/daemon/jsondbquery.h
@@ -149,6 +149,7 @@ public:
const QList<QueryTerm> &terms() const { return mTerms; }
void addTerm(const QueryTerm &term) { mTerms.append(term); }
QList<QString> propertyNames() const;
+ QList<QString> findUnindexablePropertyNames() const;
private:
QList<QueryTerm> mTerms;
};
diff --git a/src/daemon/jsondbreducedefinition.cpp b/src/daemon/jsondbreducedefinition.cpp
index 04d6a4b..1021413 100644
--- a/src/daemon/jsondbreducedefinition.cpp
+++ b/src/daemon/jsondbreducedefinition.cpp
@@ -73,22 +73,27 @@ JsonDbReduceDefinition::JsonDbReduceDefinition(const JsonDbOwner *owner, JsonDbP
, mTargetType(mDefinition.value("targetType").toString())
, mTargetTable(mPartition->findObjectTable(mTargetType))
, mSourceType(mDefinition.value("sourceType").toString())
- , mTargetKeyName(mDefinition.contains("targetKeyName") ? mDefinition.value("targetKeyName").toString() : QString("key"))
- , mTargetValueName(mDefinition.contains("targetValueName") ? mDefinition.value("targetValueName").toString() : QString("value"))
- , mSourceKeyName(mDefinition.contains("sourceKeyName") ? mDefinition.value("sourceKeyName").toString() : QString("key"))
- , mSourceKeyNameList(mSourceKeyName.split("."))
{
+ if (mDefinition.contains("targetKeyName"))
+ mTargetKeyName = mDefinition.value("targetKeyName").toString();
+ else
+ mTargetKeyName = QLatin1String("key");
+ if (mDefinition.contains("sourceKeyName"))
+ mSourceKeyName = mDefinition.value("sourceKeyName").toString();
+ mSourceKeyNameList = mSourceKeyName.split(".");
+ if (mDefinition.contains("targetValueName")) {
+ if (mDefinition.value("targetValueName").isString())
+ mTargetValueName = mDefinition.value("targetValueName").toString();
+ } else
+ mTargetValueName = QLatin1String("value");
+
}
void JsonDbReduceDefinition::definitionCreated()
{
- // TODO: this index should not be automatic
- mTargetTable->addIndexOnProperty(mSourceKeyName, QLatin1String("string"), mSourceType);
- // TODO: this index should not be automatic
- mTargetTable->addIndexOnProperty(mTargetKeyName, QLatin1String("string"), mTargetType);
- mTargetTable->addIndexOnProperty(QLatin1String("_reduceUuid"), QLatin1String("string"), mTargetType);
-
initScriptEngine();
+ initIndexes();
+
GetObjectsResult getObjectResponse = mPartition->getObjects(JsonDbString::kTypeStr, mSourceType);
if (!getObjectResponse.error.isNull()) {
if (jsondbSettings->verbose())
@@ -118,46 +123,43 @@ void JsonDbReduceDefinition::initScriptEngine()
return;
mScriptEngine = new QJSEngine(this);
+ QString message;
+ bool status = compileFunctions(mScriptEngine, mDefinition, mFunctions, message);
+ if (!status)
+ setError(message);
+
Q_ASSERT(!mDefinition.value("add").toString().isEmpty());
Q_ASSERT(!mDefinition.value("subtract").toString().isEmpty());
QJSValue globalObject = mScriptEngine->globalObject();
globalObject.setProperty("console", mScriptEngine->newQObject(new Console()));
-
- QString script = mDefinition.value("add").toString();
- mAddFunction = mScriptEngine->evaluate(QString("var %1 = (%2); %1;").arg("add").arg(script));
-
- if (mAddFunction.isError() || !mAddFunction.isCallable()) {
- setError("Unable to parse add function: " + mAddFunction.toString());
- return;
- }
-
- script = mDefinition.value("subtract").toString();
- mSubtractFunction = mScriptEngine->evaluate(QString("var %1 = (%2); %1;").arg("subtract").arg(script));
-
- if (mSubtractFunction.isError() || !mSubtractFunction.isCallable())
- setError("Unable to parse subtract function: " + mSubtractFunction.toString());
}
void JsonDbReduceDefinition::releaseScriptEngine()
{
- mAddFunction = QJSValue();
- mSubtractFunction = QJSValue();
+ mFunctions.clear();
delete mScriptEngine;
mScriptEngine = 0;
}
+void JsonDbReduceDefinition::initIndexes()
+{
+ // TODO: this index should not be automatic
+ if (!mSourceKeyName.isEmpty()) {
+ JsonDbObjectTable *sourceTable = mPartition->findObjectTable(mSourceType);
+ sourceTable->addIndexOnProperty(mSourceKeyName, QLatin1String("string"));
+ }
+ // TODO: this index should not be automatic
+ mTargetTable->addIndexOnProperty(mTargetKeyName, QLatin1String("string"), mTargetType);
+ mTargetTable->addIndexOnProperty(QLatin1String("_reduceUuid"), QLatin1String("string"), mTargetType);
+}
+
void JsonDbReduceDefinition::updateObject(JsonDbObject before, JsonDbObject after)
{
initScriptEngine();
- Q_ASSERT(mAddFunction.isCallable());
- QJsonValue beforeKeyValue = mSourceKeyName.contains(".")
- ? before.propertyLookup(mSourceKeyNameList)
- : before.value(mSourceKeyName);
- QJsonValue afterKeyValue = mSourceKeyName.contains(".")
- ? after.propertyLookup(mSourceKeyNameList)
- : after.value(mSourceKeyName);
+ QJsonValue beforeKeyValue = sourceKeyValue(before);
+ QJsonValue afterKeyValue = sourceKeyValue(after);
if (!after.isEmpty() && !before.isEmpty() && (beforeKeyValue != afterKeyValue)) {
// do a subtract only on the before key
@@ -191,9 +193,9 @@ void JsonDbReduceDefinition::updateObject(JsonDbObject before, JsonDbObject afte
QJsonValue value = previousValue;
if (!before.isEmpty())
- value = subtractObject(keyValue, value, before);
+ value = addObject(JsonDbReduceDefinition::Subtract, keyValue, value, before);
if (!after.isEmpty())
- value = addObject(keyValue, value, after);
+ value = addObject(JsonDbReduceDefinition::Add, keyValue, value, after);
JsonDbWriteResult res;
// if we had a previous object to reduce
@@ -229,21 +231,28 @@ void JsonDbReduceDefinition::updateObject(JsonDbObject before, JsonDbObject afte
setError(QString("Error executing add function: %1").arg(res.message));
}
-QJsonValue JsonDbReduceDefinition::addObject(const QJsonValue &keyValue, const QJsonValue &previousValue, JsonDbObject object)
+QJsonValue JsonDbReduceDefinition::addObject(JsonDbReduceDefinition::FunctionNumber functionNumber,
+ const QJsonValue &keyValue, QJsonValue previousValue, JsonDbObject object)
{
initScriptEngine();
QJSValue svKeyValue = JsonDbObject::toJSValue(keyValue, mScriptEngine);
- QJSValue svPreviousValue = JsonDbObject::toJSValue(previousValue.toObject().value(mTargetValueName), mScriptEngine);
+ if (!mTargetValueName.isEmpty())
+ previousValue = previousValue.toObject().value(mTargetValueName);
+ QJSValue svPreviousValue = JsonDbObject::toJSValue(previousValue, mScriptEngine);
QJSValue svObject = JsonDbObject::toJSValue(object, mScriptEngine);
QJSValueList reduceArgs;
reduceArgs << svKeyValue << svPreviousValue << svObject;
- QJSValue reduced = mAddFunction.call(reduceArgs);
+ QJSValue reduced = mFunctions[functionNumber].call(reduceArgs);
if (!reduced.isUndefined() && !reduced.isError()) {
- QJsonObject jsonReduced;
- jsonReduced.insert(mTargetValueName, JsonDbObject::fromJSValue(reduced));
- return jsonReduced;
+ QJsonValue jsonReduced = JsonDbObject::fromJSValue(reduced);
+ QJsonObject jsonReducedObject;
+ if (!mTargetValueName.isEmpty())
+ jsonReducedObject.insert(mTargetValueName, jsonReduced);
+ else
+ jsonReducedObject = jsonReduced.toObject();
+ return jsonReducedObject;
} else {
if (reduced.isError())
@@ -253,31 +262,6 @@ QJsonValue JsonDbReduceDefinition::addObject(const QJsonValue &keyValue, const Q
}
}
-QJsonValue JsonDbReduceDefinition::subtractObject(const QJsonValue &keyValue, const QJsonValue &previousValue, JsonDbObject object)
-{
- initScriptEngine();
- Q_ASSERT(mSubtractFunction.isCallable());
-
- QJSValue svKeyValue = JsonDbObject::toJSValue(keyValue, mScriptEngine);
- QJSValue svPreviousValue = JsonDbObject::toJSValue(previousValue.toObject().value(mTargetValueName),
- mScriptEngine);
- QJSValue sv = JsonDbObject::toJSValue(object, mScriptEngine);
-
- QJSValueList reduceArgs;
- reduceArgs << svKeyValue << svPreviousValue << sv;
- QJSValue reduced = mSubtractFunction.call(reduceArgs);
-
- if (!reduced.isUndefined() && !reduced.isError()) {
- QJsonObject jsonReduced;
- jsonReduced.insert(mTargetValueName, JsonDbObject::fromJSValue(reduced));
- return jsonReduced;
- } else {
- if (reduced.isError())
- setError("Error executing subtract function: " + reduced.toString());
- return QJsonValue(QJsonValue::Undefined);
- }
-}
-
bool JsonDbReduceDefinition::isActive() const
{
return !mDefinition.contains(JsonDbString::kActiveStr) || mDefinition.value(JsonDbString::kActiveStr).toBool();
@@ -309,14 +293,67 @@ bool JsonDbReduceDefinition::validateDefinition(const JsonDbObject &reduce, Json
&& view->mReduceDefinitionsBySource.value(sourceType)->uuid() != uuid)
message = QString("duplicate Reduce definition on source %1 and target %2")
.arg(sourceType).arg(targetType);
- else if (reduce.value("sourceKeyName").toString().isEmpty())
- message = QLatin1Literal("sourceKeyName property for Reduce not specified");
+ else if (reduce.value("sourceKeyName").toString().isEmpty() && reduce.value("sourceKeyFunction").toString().isEmpty())
+ message = QLatin1Literal("sourceKeyName or sourceKeyFunction must be provided for Reduce");
+ else if (!reduce.value("sourceKeyName").toString().isEmpty() && !reduce.value("sourceKeyFunction").toString().isEmpty())
+ message = QLatin1Literal("Only one of sourceKeyName and sourceKeyFunction may be provided for Reduce");
else if (reduce.value("add").toString().isEmpty())
message = QLatin1Literal("add function for Reduce not specified");
else if (reduce.value("subtract").toString().isEmpty())
message = QLatin1Literal("subtract function for Reduce not specified");
-
+ else if (reduce.contains("targetValueName")
+ && !(reduce.value("targetValueName").isString() || reduce.value("targetValueName").isNull()))
+ message = QLatin1Literal("targetValueName for Reduce must be a string or null");
+ else {
+ // FIXME: This is static because otherwise we leak memory per QJSEngine instance
+ static QJSEngine *scriptEngine = new QJSEngine;
+ QVector<QJSValue> functions;
+ // check for script errors
+ compileFunctions(scriptEngine, reduce, functions, message);
+ scriptEngine->collectGarbage();
+ }
return message.isEmpty();
}
+bool JsonDbReduceDefinition::compileFunctions(QJSEngine *scriptEngine, QJsonObject definition,
+ QVector<QJSValue> &functions, QString &message)
+{
+ bool status = true;
+ QStringList functionNames = (QStringList()
+ << QLatin1String("add")
+ << QLatin1String("subtract")
+ << QLatin1String("sourceKeyFunction"));
+ int i = 0;
+ functions.resize(3);
+ foreach (const QString &functionName, functionNames) {
+ int functionNumber = i++;
+ if (!definition.contains(functionName))
+ continue;
+ QString script = definition.value(functionName).toString();
+ QJSValue result = scriptEngine->evaluate(QString("(%1)").arg(script));
+
+ if (result.isError() || !result.isCallable()) {
+ message = QString("Unable to parse add function: %1").arg(result.toString());
+ status = false;
+ continue;
+ }
+ functions[functionNumber] = result;
+ }
+ return status;
+}
+
+QJsonValue JsonDbReduceDefinition::sourceKeyValue(const QJsonObject &object)
+{
+ if (object.isEmpty()) {
+ return QJsonValue(QJsonValue::Undefined);
+ } else if (mFunctions[JsonDbReduceDefinition::SourceKeyValue].isCallable()) {
+ QJSValueList args;
+ args << JsonDbObject::toJSValue(object, mScriptEngine);
+ QJsonValue keyValue = JsonDbObject::fromJSValue(mFunctions[JsonDbReduceDefinition::SourceKeyValue].call(args));
+ return keyValue;
+ } else
+ return mSourceKeyName.contains(".") ? JsonDbObject(object).propertyLookup(mSourceKeyNameList) : object.value(mSourceKeyName);
+
+}
+
QT_END_NAMESPACE_JSONDB
diff --git a/src/daemon/jsondbreducedefinition.h b/src/daemon/jsondbreducedefinition.h
index e8f004f..214153e 100644
--- a/src/daemon/jsondbreducedefinition.h
+++ b/src/daemon/jsondbreducedefinition.h
@@ -77,8 +77,6 @@ public:
QStringList sourceKeyNameList() const { return mSourceKeyNameList; }
bool isActive() const;
QJsonObject definition() const { return mDefinition; }
- const QJSValue &addFunction() const { return mAddFunction; }
- const QJSValue &subtractFunction() const { return mSubtractFunction; }
const JsonDbOwner *owner() const { return mOwner; }
static void definitionRemoved(JsonDbPartition *partition, JsonDbObjectTable *table, const QString targetType, const QString &definitionUuid);
@@ -86,21 +84,30 @@ public:
void initScriptEngine();
void releaseScriptEngine();
+ void initIndexes();
+
void updateObject(JsonDbObject before, JsonDbObject after);
- QJsonValue addObject(const QJsonValue &keyValue, const QJsonValue &previousResult, JsonDbObject object);
- QJsonValue subtractObject(const QJsonValue &keyValue, const QJsonValue &previousResult, JsonDbObject object);
void setError(const QString &errorMsg);
static bool validateDefinition(const JsonDbObject &reduce, JsonDbPartition *partition, QString &message);
private:
+ enum FunctionNumber {
+ Add = 0,
+ Subtract = 1,
+ SourceKeyValue = 2
+ };
+ static bool compileFunctions(QJSEngine *scriptEngine, QJsonObject definition, QVector<QJSValue> &mFunctions, QString &message);
+ QJsonValue sourceKeyValue(const QJsonObject &object);
+ QJsonValue addObject(FunctionNumber fn, const QJsonValue &keyValue, QJsonValue previousResult, JsonDbObject object);
+
+private:
const JsonDbOwner *mOwner;
JsonDbPartition *mPartition;
QJsonObject mDefinition;
QJSEngine *mScriptEngine;
- QJSValue mAddFunction;
- QJSValue mSubtractFunction;
+ QVector<QJSValue> mFunctions;
QString mUuid;
QString mTargetType;
JsonDbObjectTable *mTargetTable;
diff --git a/src/daemon/jsondbview.cpp b/src/daemon/jsondbview.cpp
index 4ca6e24..4adc5d6 100644
--- a/src/daemon/jsondbview.cpp
+++ b/src/daemon/jsondbview.cpp
@@ -159,18 +159,13 @@ void JsonDbView::createMapDefinition(QJsonObject mapDefinition)
qDebug() << "createMapDefinition" << uuid << targetType << "{";
JsonDbOwner *owner = new JsonDbOwner(this);
- owner->setOwnerCapabilities(mapDefinition.value(JsonDbString::kOwnerStr).toString(), mPartition);
+ owner->setAllowAll(true);
JsonDbMapDefinition *def = new JsonDbMapDefinition(owner, mPartition, mapDefinition, this);
+ def->initIndexes();
+
QStringList sourceTypes = def->sourceTypes();
for (int i = 0; i < sourceTypes.size(); i++) {
const QString sourceType = sourceTypes[i];
- if (mMapDefinitionsBySource.contains(sourceType)) {
- qWarning() << QString("Duplicate Map definition on source %1 and target %2")
- .arg(sourceType).arg(targetType);
- def->setError(QString("Duplicate Map definition on source %1 and target %2")
- .arg(sourceType).arg(targetType));
- return;
- }
mMapDefinitionsBySource.insert(sourceType, def);
}
mMapDefinitions.insert(def->uuid(), def);
@@ -192,7 +187,6 @@ void JsonDbView::removeMapDefinition(QJsonObject mapDefinition)
const QStringList &sourceTypes = def->sourceTypes();
for (int i = 0; i < sourceTypes.size(); i++)
mMapDefinitionsBySource.remove(sourceTypes[i]);
-
break;
}
}
@@ -210,14 +204,9 @@ void JsonDbView::createReduceDefinition(QJsonObject reduceDefinition)
qDebug() << "createReduceDefinition" << sourceType << targetType << sourceType << "{";
JsonDbOwner *owner = new JsonDbOwner(this);
- owner->setOwnerCapabilities(reduceDefinition.value(JsonDbString::kOwnerStr).toString(), mPartition);
+ owner->setAllowAll(true);
JsonDbReduceDefinition *def = new JsonDbReduceDefinition(owner, mPartition, reduceDefinition, this);
- if (mReduceDefinitionsBySource.contains(sourceType)) {
- def->setError(QString("Duplicate Reduce definition on source %1 and target %2")
- .arg(sourceType).arg(targetType));
- return;
- }
-
+ def->initIndexes();
mReduceDefinitionsBySource.insert(sourceType, def);
mReduceDefinitions.insert(def->uuid(), def);
@@ -381,6 +370,8 @@ void JsonDbView::updateView(quint32 desiredStateNumber)
if (jsondbSettings->performanceLog())
qDebug() << "updateView" << mViewType << timer.elapsed() << "ms";
mUpdating = false;
+
+ emit updated(mViewType);
}
bool JsonDbView::processUpdatedDefinitions(const QString &viewType, quint32 targetStateNumber,
@@ -431,10 +422,12 @@ bool JsonDbView::processUpdatedDefinitions(const QString &viewType, quint32 targ
}
definitionUuid = after.value(JsonDbString::kUuidStr).toString();
QString definitionType = after.value(JsonDbString::kTypeStr).toString();
- if (definitionType == JsonDbString::kMapTypeStr)
- mMapDefinitions.value(definitionUuid)->definitionCreated();
- else
- mReduceDefinitions.value(definitionUuid)->definitionCreated();
+ if (!after.contains(JsonDbString::kActiveStr) || after.value(JsonDbString::kActiveStr).toBool()) {
+ if (definitionType == JsonDbString::kMapTypeStr)
+ mMapDefinitions.value(definitionUuid)->definitionCreated();
+ else
+ mReduceDefinitions.value(definitionUuid)->definitionCreated();
+ }
}
}
if (!definitionUuid.isEmpty())
diff --git a/src/daemon/jsondbview.h b/src/daemon/jsondbview.h
index 2f2efbc..a2d5675 100644
--- a/src/daemon/jsondbview.h
+++ b/src/daemon/jsondbview.h
@@ -80,6 +80,9 @@ public:
bool isActive() const;
+Q_SIGNALS:
+ void updated(const QString &type);
+
private:
void createMapDefinition(QJsonObject mapDefinition);
void removeMapDefinition(QJsonObject mapDefinition);
diff --git a/src/daemon/schema/Index.json b/src/daemon/schema/Index.json
index d98cd5b..7c60d15 100644
--- a/src/daemon/schema/Index.json
+++ b/src/daemon/schema/Index.json
@@ -18,7 +18,6 @@
"description": "Type of values stored in that property."
},
"objectType": {
- "type": "string",
"description": "Object type to index. Optional."
}
}
diff --git a/src/imports/jsondb-listmodel/jsondb-listmodel.pro b/src/imports/jsondb-listmodel/jsondb-listmodel.pro
index b881c12..9e86677 100644
--- a/src/imports/jsondb-listmodel/jsondb-listmodel.pro
+++ b/src/imports/jsondb-listmodel/jsondb-listmodel.pro
@@ -13,6 +13,18 @@ qmldir.path += $$[QT_INSTALL_IMPORTS]/$$TARGETPATH
INSTALLS += target qmldir
+#rules for qmltypes
+!cross_compile {
+ qtPrepareTool(QMLPLUGINDUMP, qmlplugindump)
+ QMLTYPESFILE = $$QT.jsondb.imports/$$TARGETPATH/plugin.qmltypes
+ mac: !exists($$QMLPLUGINDUMP): QMLPLUGINDUMP = "$${QMLPLUGINDUMP}.app/Contents/MacOS/qmlplugindump"
+ QMAKE_POST_LINK += LD_LIBRARY_PATH=$$QT.jsondb.libs $$QMLPLUGINDUMP QtAddOn.JsonDb 1.0 $$QT.jsondb.imports > $$QMLTYPESFILE
+
+ qmltypes.files = $$QMLTYPESFILE
+ qmltypes.path = $$[QT_INSTALL_IMPORTS]/$$TARGETPATH
+ INSTALLS += qmltypes
+}
+
VERSION = 1.0
include(../../common/common.pri)
diff --git a/src/imports/jsondb/jsondb.pro b/src/imports/jsondb/jsondb.pro
index adb7ce2..781e3d1 100644
--- a/src/imports/jsondb/jsondb.pro
+++ b/src/imports/jsondb/jsondb.pro
@@ -13,6 +13,18 @@ qmldir.path += $$[QT_INSTALL_IMPORTS]/$$TARGETPATH
INSTALLS += target qmldir
+#rules for qmltypes
+!cross_compile {
+ qtPrepareTool(QMLPLUGINDUMP, qmlplugindump)
+ mac: !exists($$QMLPLUGINDUMP): QMLPLUGINDUMP = "$${QMLPLUGINDUMP}.app/Contents/MacOS/qmlplugindump"
+ QMLTYPESFILE = $$QT.jsondbcompat.imports/$$TARGETPATH/plugin.qmltypes
+ QMAKE_POST_LINK += LD_LIBRARY_PATH=$$QT.jsondbcompat.libs $$QMLPLUGINDUMP QtJsonDb 1.0 $$QT.jsondbcompat.imports > $$QMLTYPESFILE
+
+ qmltypes.files = $$QMLTYPESFILE
+ qmltypes.path = $$[QT_INSTALL_IMPORTS]/$$TARGETPATH
+ INSTALLS += qmltypes
+}
+
VERSION = 1.0
include(../../common/common.pri)
diff --git a/src/imports/jsondb/jsondbqueryobject.cpp b/src/imports/jsondb/jsondbqueryobject.cpp
index f9e2a46..b42daaa 100644
--- a/src/imports/jsondb/jsondbqueryobject.cpp
+++ b/src/imports/jsondb/jsondbqueryobject.cpp
@@ -124,10 +124,6 @@ void JsonDbQueryObject::setQuery(const QString &newQuery)
JsonDbPartition* JsonDbQueryObject::partition()
{
- if (!partitionObject) {
- defaultPartitionObject = new JsonDbPartition();
- setPartition(defaultPartitionObject);
- }
checkForReadyStatus();
return partitionObject;
}
@@ -384,8 +380,10 @@ void JsonDbQueryObject::checkForReadyStatus()
JsonDbQueryObject::Status oldStatus = objectStatus;
- if (!partitionObject)
- partitionObject = qobject_cast<JsonDbPartition*>(parent());
+ if (!partitionObject) {
+ defaultPartitionObject = new JsonDbPartition();
+ setPartition(defaultPartitionObject);
+ }
if (!parametersReady()) {
objectStatus = JsonDbQueryObject::Null;
if (objectStatus != oldStatus)
diff --git a/tests/auto/accesscontrol/json/capabilities-view.json b/tests/auto/accesscontrol/json/capabilities-view.json
new file mode 100644
index 0000000..be060de
--- /dev/null
+++ b/tests/auto/accesscontrol/json/capabilities-view.json
@@ -0,0 +1,24 @@
+[
+ {
+ "_type": "Capability",
+ "name": "views",
+ "partition": "all",
+ "accessRules": {
+ "rw": {
+ "read": ["[?_type startsWith \"Phone\"]", "[?_type = \"Contact\"]", "[?_type = \"_schemaType\"]", "[?_type = \"Map\"]", "[?_type = \"Reduce\"]"],
+ "write": ["[?_type startsWith \"Phone\"]", "[?_type = \"Contact\"]", "[?_type = \"_schemaType\"]", "[?_type = \"Map\"]", "[?_type = \"Reduce\"]"]
+ }
+ }
+ },
+{
+ "_type": "Capability",
+ "name": "noviews",
+ "partition": "all",
+ "accessRules": {
+ "rw": {
+ "read": ["[?_type = \"Contact\"]", "[?_type = \"_schemaType\"]", "[?_type = \"Map\"]", "[?_type = \"Reduce\"]"],
+ "write": ["[?_type = \"Contact\"]", "[?_type = \"_schemaType\"]", "[?_type = \"Map\"]", "[?_type = \"Reduce\"]", "[?_type = \"PhoneCount3\"]"]
+ }
+ }
+}
+]
diff --git a/tests/auto/accesscontrol/json/view-test.json b/tests/auto/accesscontrol/json/view-test.json
new file mode 100644
index 0000000..223d5a4
--- /dev/null
+++ b/tests/auto/accesscontrol/json/view-test.json
@@ -0,0 +1,54 @@
+[
+{
+ "_type": "_schemaType",
+ "name": "Phone",
+ "schema": {
+ "type": "object",
+ "extends": "View"
+ }
+},
+{
+ "_type": "_schemaType",
+ "name": "PhoneCount",
+ "schema": {
+ "type": "object",
+ "extends": "View"
+ }
+},
+{
+ "_type": "_schemaType",
+ "name": "Phone3",
+ "schema": {
+ "type": "object",
+ "extends": "View"
+ }
+},
+{
+ "_type": "_schemaType",
+ "name": "PhoneCount3",
+ "schema": {
+ "type": "object",
+ "extends": "View"
+ }
+},
+{
+ "_type": "Map",
+ "targetType": "Phone",
+ "map": {"Contact": "function (c) {\
+ for (var i in c.phoneNumbers) {\
+ var phone = c.phoneNumbers[i];\
+ var id = c.displayName + ':' + phone.number; \
+ var uuid = jsondb.createUuidFromString(id); \
+ jsondb.emit({_uuid: uuid, key: phone.number, displayName: c.displayName});\
+ }\
+ }"}
+},
+{
+ "_type": "Reduce",
+ "targetType": "PhoneCount",
+ "sourceType": "Phone",
+ "sourceKeyName": "key",
+ "add": "function add (k, z, c) { if (!z) {z = {count: 0}}; z.count += 1; return z;}",
+ "subtract": "function subtract (k, z, c) { if (!z) {z = {count: 0}}; z.count -= 1; if (z.count) return z;}"
+}
+]
diff --git a/tests/auto/accesscontrol/json/view-test2.json b/tests/auto/accesscontrol/json/view-test2.json
new file mode 100644
index 0000000..c3e92b3
--- /dev/null
+++ b/tests/auto/accesscontrol/json/view-test2.json
@@ -0,0 +1,38 @@
+[
+{
+ "_type": "_schemaType",
+ "name": "Phone2",
+ "schema": {
+ "type": "object",
+ "extends": "View"
+ }
+},
+{
+ "_type": "_schemaType",
+ "name": "PhoneCount2",
+ "schema": {
+ "type": "object",
+ "extends": "View"
+ }
+},
+{
+ "_type": "Map",
+ "targetType": "Phone3",
+ "map": {"Contact": "function (c) {\
+ for (var i in c.phoneNumbers) {\
+ var phone = c.phoneNumbers[i];\
+ var id = c.displayName + ':' + phone.number; \
+ var uuid = jsondb.createUuidFromString(id); \
+ jsondb.emit({_uuid: uuid, key: phone.number, displayName: c.displayName});\
+ }\
+ }"}
+},
+{
+ "_type": "Reduce",
+ "targetType": "PhoneCount3",
+ "sourceType": "Phone",
+ "sourceKeyName": "key",
+ "add": "function add (k, z, c) { if (!z) {z = {count: 0}}; z.count += 1; return z;}",
+ "subtract": "function subtract (k, z, c) { if (!z) {z = {count: 0}}; z.count -= 1; if (z.count) return z;}"
+}
+]
diff --git a/tests/auto/accesscontrol/testjsondb.cpp b/tests/auto/accesscontrol/testjsondb.cpp
index ef6238f..4339934 100644
--- a/tests/auto/accesscontrol/testjsondb.cpp
+++ b/tests/auto/accesscontrol/testjsondb.cpp
@@ -102,6 +102,7 @@ private slots:
void testAccessControl();
void testFindAccessControl();
+ void testViewAccessControl();
private:
JsonDbQueryResult find(JsonDbOwner *owner, const QString &query, const QJsonObject bindings = QJsonObject());
@@ -434,6 +435,50 @@ void TestJsonDb::testFindAccessControl()
jsondbSettings->setEnforceAccessControl(false);
}
+/*
+ * Create Map and Reduce objects and check access control
+ */
+
+void TestJsonDb::testViewAccessControl()
+{
+ jsondbSettings->setEnforceAccessControl(false);
+ QJsonArray defs(readJsonFile(":/security/json/capabilities-view.json").toArray());
+ for (int i = 0; i < defs.size(); ++i) {
+ JsonDbObject object(defs.at(i).toObject());
+ JsonDbWriteResult result = create(mOwner, object);
+ verifyGoodResult(result);
+ }
+
+ jsondbSettings->setEnforceAccessControl(true);
+ QJsonObject viewCapabilities;
+ QJsonArray value;
+ value.append (QLatin1String("rw"));
+ viewCapabilities.insert (QLatin1String("views"), value);
+ mOwner->setAllowAll(false);
+ mOwner->setCapabilities(viewCapabilities, mJsonDbPartition);
+
+ defs = readJsonFile(":/security/json/view-test.json").toArray();
+ for (int i = 0; i < defs.size(); ++i) {
+ JsonDbObject object(defs.at(i).toObject());
+ JsonDbWriteResult result = create(mOwner, object);
+ verifyGoodResult(result);
+ }
+
+ QJsonObject novwCapabilities;
+ QJsonArray novwValue;
+ novwValue.append (QLatin1String("rw"));
+ novwCapabilities.insert (QLatin1String("noviews"), novwValue);
+ mOwner->setAllowAll(false);
+ mOwner->setCapabilities(novwCapabilities, mJsonDbPartition);
+
+ defs = readJsonFile(":/security/json/view-test2.json").toArray();
+ for (int i = 0; i < defs.size(); ++i) {
+ JsonDbObject object(defs.at(i).toObject());
+ JsonDbWriteResult result = create(mOwner, object);
+ verifyErrorResult(result);
+ }
+}
+
QStringList strings = (QStringList()
<< "abc"
<< "def"
@@ -456,7 +501,7 @@ QJsonValue TestJsonDb::readJsonFile(const QString& filename)
JsonReader parser;
bool ok = parser.parse(json);
if (!ok) {
- qDebug() << filepath << parser.errorString();
+ qDebug() << filepath << parser.errorString() << json;
}
QVariant v = parser.result();
return QJsonValue::fromVariant(v);
diff --git a/tests/auto/client/test-jsondb-client.cpp b/tests/auto/client/test-jsondb-client.cpp
index 40946c4..81290c5 100644
--- a/tests/auto/client/test-jsondb-client.cpp
+++ b/tests/auto/client/test-jsondb-client.cpp
@@ -98,6 +98,7 @@ private slots:
void update();
void find();
void index();
+ void multiTypeIndex();
void registerNotification();
void notify_data();
@@ -267,7 +268,7 @@ void TestJsonDbClient::initTestCase()
QLatin1String("[?_type = \"_schemaType\"]") <<
QLatin1String("[?_type = \"Index\"]") <<
QLatin1String("[?_type = \"Partition\"]") <<
- QLatin1String("[?_type = \"Phone\"]") <<
+ QLatin1String("[?_type startsWith \"Phone\"]") <<
QLatin1String("[?_type = \"Contact\"]") <<
QLatin1String("[?_type = \"Reduce\"]") <<
QLatin1String("[?_type = \"Map\"]")));
@@ -395,21 +396,24 @@ void TestJsonDbClient::initTestCase()
gid_t gid = nextFreeGid(1042);
gid_t gid2 = nextFreeGid(gid+1);
QString appName = QString("com.test.foo.%1").arg(getpid());
+ QByteArray appNameBA = appName.toLocal8Bit();
// Tes app name for app without any supplementary groups
QString app2Name = QString("com.test.bar.%1").arg(getpid());
+ QByteArray app2NameBA = app2Name.toLocal8Bit();
if (!errno) {
// Add primary groups
struct group grp;
- grp.gr_name = appName.toLocal8Bit().data();
+ grp.gr_name = appNameBA.data();
grp.gr_passwd = NULL;
grp.gr_gid = gid;
grp.gr_mem = (char *[]){NULL};
struct group grp2;
- grp2.gr_name = app2Name.toLocal8Bit().data();
+ grp2.gr_name = app2NameBA.data();
grp2.gr_passwd = NULL;
grp2.gr_gid = gid2;
grp2.gr_mem = (char *[]){NULL};
- FILE *grfile = ::fopen (etcigr.toLocal8Bit().data(), "a");
+ QByteArray etcigrBA = etcigr.toLocal8Bit();
+ FILE *grfile = ::fopen (etcigrBA.data(), "a");
::putgrent(&grp, grfile);
::putgrent(&grp2, grfile);
::fclose (grfile);
@@ -418,7 +422,7 @@ void TestJsonDbClient::initTestCase()
// Add the user
struct passwd pwd;
- pwd.pw_name = appName.toLocal8Bit().data();
+ pwd.pw_name = appNameBA.data();
pwd.pw_passwd = NULL;
pwd.pw_uid = uid;
pwd.pw_gid = gid;
@@ -426,14 +430,15 @@ void TestJsonDbClient::initTestCase()
pwd.pw_dir = NULL;
pwd.pw_shell = NULL;
struct passwd pwd2;
- pwd2.pw_name = app2Name.toLocal8Bit().data();
+ pwd2.pw_name = app2NameBA.data();
pwd2.pw_passwd = NULL;
pwd2.pw_uid = uid2;
pwd2.pw_gid = gid2;
pwd2.pw_gecos = NULL;
pwd2.pw_dir = NULL;
pwd2.pw_shell = NULL;
- FILE *pwdfile = ::fopen (etcipwd.toLocal8Bit().data(), "a");
+ QByteArray etcipwdBA = etcipwd.toLocal8Bit();
+ FILE *pwdfile = ::fopen (etcipwdBA.data(), "a");
::putpwent(&pwd, pwdfile);
::putpwent(&pwd2, pwdfile);
::fclose (pwdfile);
@@ -444,8 +449,8 @@ void TestJsonDbClient::initTestCase()
grp.gr_passwd = NULL;
grp.gr_gid = gid;
// Add only the first user to it
- grp.gr_mem = (char *[]){appName.toLocal8Bit().data(), NULL};
- grfile = ::fopen (etcigr.toLocal8Bit(), "a");
+ grp.gr_mem = (char *[]){appNameBA.data(), NULL};
+ grfile = ::fopen (etcigrBA.data(), "a");
::putgrent(&grp, grfile);
::fclose (grfile);
gidsAdded.append(gid);
@@ -692,6 +697,15 @@ void TestJsonDbClient::find()
int id = 0;
int count;
+ // create an index on the name property of com.test.NameIndex objects
+ QVariantMap index;
+ index.insert(JsonDbString::kTypeStr, JsonDbString::kIndexTypeStr);
+ index.insert(JsonDbString::kNameStr, QLatin1String("com.test.NameIndex"));
+ index.insert(JsonDbString::kPropertyNameStr, QLatin1String("name"));
+ index.insert(JsonDbString::kObjectTypeStr, QLatin1String("com.test.find-test"));
+ id = mClient->create(index);
+ waitForResponse1(id);
+
QStringList nameList;
// Create a few items
for (count = 0 ; names[count] ; count++ ) {
@@ -724,7 +738,7 @@ void TestJsonDbClient::find()
// Find one, sorted in reverse alphabetical order
query = QVariantMap();
- query.insert("query", "[?_type=\"com.test.find-test\"][\\name]");
+ query.insert("query", "[?_type=\"com.test.find-test\"][\\com.test.NameIndex]");
id = mClient->find(query);
waitForResponse1(id);
answer = mData.toMap().value("data").toList();
@@ -752,7 +766,7 @@ void TestJsonDbClient::find()
// Read should fail
query = QVariantMap();
- query.insert("query", "[?_type=\"com.test.find-test\"][\\name]");
+ query.insert("query", "[?_type=\"com.test.find-test\"][\\com.test.NameIndex]");
id = mClient->find(query);
waitForResponse1(id);
answer = mData.toMap().value("data").toList();
@@ -823,6 +837,55 @@ void TestJsonDbClient::index()
waitForResponse1(id);
}
+void TestJsonDbClient::multiTypeIndex()
+{
+ QVERIFY(mClient);
+
+ QVariantMap query;
+ int id = 0;
+ int count;
+
+ QStringList indexedTypes = (QStringList()
+ << QLatin1String("com.test.find-test1")
+ << QLatin1String("com.test.find-test2"));
+ // create an index on the name property of com.test.NameIndex objects
+ QVariantMap index;
+ index.insert(JsonDbString::kTypeStr, JsonDbString::kIndexTypeStr);
+ index.insert(JsonDbString::kNameStr, QLatin1String("com.test.MultiTypeIndex"));
+ index.insert(JsonDbString::kPropertyNameStr, QLatin1String("name"));
+ index.insert(JsonDbString::kObjectTypeStr, indexedTypes);
+ id = mClient->create(index);
+ waitForResponse1(id);
+
+ QStringList objectTypes = (QStringList()
+ << QLatin1String("com.test.find-test1")
+ << QLatin1String("com.test.find-test2")
+ << QLatin1String("com.test.find-test3"));
+ QStringList nameList;
+ // Create a few items
+ for (count = 0 ; names[count] ; count++ ) {
+ nameList << names[count];
+ foreach (const QString objectType, objectTypes) {
+ QVariantMap item;
+ item.insert("_type", objectType);
+ item.insert("name", names[count]);
+ id = mClient->create(item);
+ waitForResponse1(id);
+ }
+ }
+
+ // Find all in the index
+ query = QVariantMap();
+ query.insert("query", "[?name exists][/com.test.MultiTypeIndex]");
+ id = mClient->find(query);
+ waitForResponse1(id);
+ QVariantList answer = mData.toMap().value("data").toList();
+ //qDebug() << "answer" << mData.toMap();
+ QCOMPARE(answer.size(), nameList.size() * 2);
+ foreach (QVariant v, answer)
+ QVERIFY(indexedTypes.contains(v.toMap().value(JsonDbString::kTypeStr).toString()));
+}
+
void TestJsonDbClient::notify_data()
{
QTest::addColumn<QString>("partition");
@@ -908,6 +971,7 @@ void TestJsonDbClient::notifyUpdate()
// Update the notify-test object
// query no longer matches, so we should receive a "remove" notification even though it is an update
+ // this means it should not contain the _deleted property
object.insert("_uuid",uuid);
object.insert("_version", version);
object.insert("filter","nomatch");
@@ -918,6 +982,7 @@ void TestJsonDbClient::notifyUpdate()
n = mNotifications.takeFirst();
QCOMPARE(n.mNotifyUuid, notifyUuid);
QCOMPARE(n.mAction, QLatin1String("remove"));
+ QVERIFY(!n.mObject.toMap().contains(JsonDbString::kDeletedStr));
// Remove the notify-test object
id = mClient->remove(object);
diff --git a/tests/auto/daemon/json/map-reduce-schema.json b/tests/auto/daemon/json/map-reduce-schema.json
index e43e49c..726d0cc 100644
--- a/tests/auto/daemon/json/map-reduce-schema.json
+++ b/tests/auto/daemon/json/map-reduce-schema.json
@@ -72,6 +72,7 @@
{
"_type": "Map",
"targetType": "Phone",
+ "targetKeyName": "key",
"brokenMap": {"Contact":"function map (c) { for (var i in c.phoneNumbers) { var phone = c.phoneNumbers[i]; jsondb.emit({key: phone.number, name: c.displayName }); }}"},
"map": {"Contact":"function map (c) { for (var i in c.phoneNumbers) { var phone = c.phoneNumbers[i]; jsondb.emit({key: phone.number, displayName: c.displayName}); }}"}
},
diff --git a/tests/auto/daemon/json/map-reduce.json b/tests/auto/daemon/json/map-reduce.json
index a04ef1c..2736a9c 100644
--- a/tests/auto/daemon/json/map-reduce.json
+++ b/tests/auto/daemon/json/map-reduce.json
@@ -46,7 +46,7 @@
"name": "Phone",
"schema": {
"type": "object",
- "extends": "View"
+ "extends": {"$ref": "View"}
}
},
{
@@ -54,7 +54,7 @@
"name": "PhoneCount",
"schema": {
"type": "object",
- "extends": "View"
+ "extends": {"$ref": "View"}
}
},
{
diff --git a/tests/auto/daemon/json/reduce.json b/tests/auto/daemon/json/reduce.json
index f95d4f1..5939fb7 100644
--- a/tests/auto/daemon/json/reduce.json
+++ b/tests/auto/daemon/json/reduce.json
@@ -20,6 +20,8 @@
"targetKeyName": "firstName",
"targetValueName": "count",
"add": "function add (k, z, c) { if (!z) {z = 0}; z += 1; if (z) return z;}",
- "subtract": "function subtract (k, z, c) { if (!z) {z = 0}; z -= 1; if (z) return z;}"
+ "subtract": "function subtract (k, z, c) { if (!z) {z = 0}; z -= 1; if (z) return z;}",
+ "addFlattened": "function add (k, z, c) { if (!z) {z = {count: 0}}; z.count += 1; if (z.count) return z;}",
+ "subtractFlattened": "function subtract (k, z, c) { if (!z) {z = {count: 0}}; z.count -= 1; if (z.count) return z;}"
}
]
diff --git a/tests/auto/daemon/testjsondb.cpp b/tests/auto/daemon/testjsondb.cpp
index e65939d..ce2781e 100644
--- a/tests/auto/daemon/testjsondb.cpp
+++ b/tests/auto/daemon/testjsondb.cpp
@@ -173,8 +173,11 @@ private slots:
void mapSelfJoinSourceUuids();
void mapMapFunctionError();
void mapSchemaViolation();
+ void mapMultipleEmitNoTargetKeyName();
void mapArrayConversion();
void reduce();
+ void reduceFlattened();
+ void reduceSourceKeyFunction();
void reduceRemoval();
void reduceUpdate();
void reduceDuplicate();
@@ -1423,7 +1426,7 @@ void TestJsonDb::mapDefinitionInvalid()
JsonDbWriteResult schemaRes = create(mOwner, schema);
verifyGoodResult(schemaRes);
- JsonDbObject mapDefinition;
+ JsonDbObject mapDefinition, map;
mapDefinition.insert(JsonDbString::kTypeStr, JsonDbString::kMapTypeStr);
mapDefinition.insert("sourceType", QLatin1String("Contact"));
mapDefinition.insert("map", QLatin1String("function map (c) { }"));
@@ -1448,8 +1451,8 @@ void TestJsonDb::mapDefinitionInvalid()
mapDefinition = JsonDbObject();
mapDefinition.insert(JsonDbString::kTypeStr, JsonDbString::kMapTypeStr);
mapDefinition.insert("targetType", QLatin1String("MyViewType2"));
- mapDefinition.insert("sourceType", QLatin1String("Contact"));
- mapDefinition.insert("map", QLatin1String("function map (c) { }"));
+ map.insert(QLatin1String("Contact"), QLatin1String("function map (c) { }"));
+ mapDefinition.insert("map", map);
res = create(mOwner, mapDefinition);
verifyErrorResult(res);
QVERIFY(res.message.contains("View"));
@@ -1555,6 +1558,17 @@ void TestJsonDb::reduceDefinitionInvalid()
verifyErrorResult(res);
QVERIFY(res.message.contains("View"));
+ // fail because targetValue name is not a string or null
+ reduceDefinition = JsonDbObject();
+ reduceDefinition.insert(JsonDbString::kTypeStr, QLatin1String("Reduce"));
+ reduceDefinition.insert("targetType", QLatin1String("MyViewType"));
+ reduceDefinition.insert("sourceType", QLatin1String("Contact"));
+ reduceDefinition.insert("add", QLatin1String("function add (k, z, c) { }"));
+ reduceDefinition.insert("subtract", QLatin1String("function subtract (k, z, c) { }"));
+ reduceDefinition.insert("targetValueName", true);
+ res = create(mOwner, reduceDefinition);
+ verifyErrorResult(res);
+
//schemaRes.value("result").toObject()
verifyGoodResult(remove(mOwner, schema));
}
@@ -1579,20 +1593,9 @@ void TestJsonDb::mapInvalidMapFunc()
mapDefinition.insert("map", sourceToMapFunctions);
JsonDbWriteResult defRes = create(mOwner, mapDefinition);
- verifyGoodResult(defRes);
- QString uuid = defRes.objectsWritten[0].uuid().toString();
-
- // force the view to be updated
- mJsonDbPartition->updateView("InvalidMapViewType");
-
- // now check for an error
- GetObjectsResult res = mJsonDbPartition->getObjects("_uuid", uuid, JsonDbString::kMapTypeStr);
- QVERIFY(res.data.size() > 0);
- mapDefinition = res.data.at(0);
- QVERIFY(mapDefinition.contains(JsonDbString::kActiveStr) && !mapDefinition.value(JsonDbString::kActiveStr).toBool());
- QVERIFY(!mapDefinition.value(JsonDbString::kErrorStr).toString().isEmpty());
+ verifyErrorResult(defRes);
+ QCOMPARE(defRes.objectsWritten.size(), 0);
- verifyGoodResult(remove(mOwner, mapDefinition));
verifyGoodResult(remove(mOwner, schema));
}
@@ -1616,16 +1619,8 @@ void TestJsonDb::reduceInvalidAddSubtractFuncs()
reduceDefinition.insert("add", QLatin1String("function add (k, z, c) { ;")); // non-parsable add function
reduceDefinition.insert("subtract", QLatin1String("function subtract (k, z, c) { }"));
JsonDbWriteResult res = create(mOwner, reduceDefinition);
- verifyGoodResult(res);
-
- mJsonDbPartition->updateView("MyViewType");
-
- GetObjectsResult getObjects = mJsonDbPartition->getObjects("_uuid", res.objectsWritten[0].uuid().toString());
- reduceDefinition = getObjects.data.at(0);
- QVERIFY(reduceDefinition.contains(JsonDbString::kActiveStr) && !reduceDefinition.value(JsonDbString::kActiveStr).toBool());
- QVERIFY(!reduceDefinition.value(JsonDbString::kErrorStr).toString().isEmpty());
+ verifyErrorResult(res);
- verifyGoodResult(remove(mOwner, reduceDefinition));
verifyGoodResult(remove(mOwner, schema));
}
@@ -2134,6 +2129,55 @@ void TestJsonDb::mapSchemaViolation()
jsondbSettings->setValidateSchemas(false);
}
+// verify that only one target object per source object is allowed without targetKeyName
+void TestJsonDb::mapMultipleEmitNoTargetKeyName()
+{
+ jsondbSettings->setValidateSchemas(true);
+
+ GetObjectsResult contactsRes = mJsonDbPartition->getObjects(JsonDbString::kTypeStr, QLatin1String("Contact"));
+ if (contactsRes.data.size() > 0) {
+ foreach (const JsonDbObject &toRemove, contactsRes.data)
+ remove(mOwner, toRemove);
+ }
+
+ QJsonArray objects(readJsonFile(":/daemon/json/map-reduce-schema.json").toArray());
+ JsonDbObjectList toDelete;
+ JsonDbObject map;
+
+ for (int ii = 0; ii < objects.size(); ii++) {
+ JsonDbObject object(objects.at(ii).toObject());
+ if (object.value(JsonDbString::kTypeStr).toString() != JsonDbString::kReduceTypeStr) {
+
+ if (object.value(JsonDbString::kTypeStr).toString() == JsonDbString::kMapTypeStr)
+ object.remove("targetKeyName");
+
+ JsonDbWriteResult result = create(mOwner, object);
+ if (object.value(JsonDbString::kTypeStr).toString() == JsonDbString::kMapTypeStr)
+ map = object;
+
+ verifyGoodResult(result);
+ if (object.value(JsonDbString::kTypeStr).toString() != JsonDbString::kMapTypeStr)
+ toDelete.append(object);
+ }
+ }
+
+ mJsonDbPartition->updateView(map.value("targetType").toString());
+
+ GetObjectsResult getObjects = mJsonDbPartition->getObjects("_uuid", map.value(JsonDbString::kUuidStr).toString());
+ QJsonObject mapDefinition = getObjects.data.at(0);
+ QVERIFY(!mapDefinition.contains(JsonDbString::kActiveStr)|| mapDefinition.value(JsonDbString::kActiveStr).toBool());
+ QVERIFY(!mapDefinition.contains(JsonDbString::kErrorStr) || mapDefinition.value(JsonDbString::kErrorStr).toString().isEmpty());
+
+ getObjects = mJsonDbPartition->getObjects(JsonDbString::kTypeStr, QLatin1String("Phone"));
+ QCOMPARE(getObjects.data.size(), 2);
+
+ verifyGoodResult(remove(mOwner, map));
+ for (int ii = 0; ii < toDelete.size(); ii++)
+ verifyGoodResult(remove(mOwner, toDelete.at(ii)));
+
+ jsondbSettings->setValidateSchemas(false);
+}
+
void TestJsonDb::mapArrayConversion()
{
QJsonArray objects(readJsonFile(":/daemon/json/map-array-conversion.json").toArray());
@@ -2200,6 +2244,104 @@ void TestJsonDb::reduce()
mJsonDbPartition->removeIndex("MyContactCount");
}
+void TestJsonDb::reduceFlattened()
+{
+ QJsonArray objects(readJsonFile(":/daemon/json/reduce-data.json").toArray());
+
+ JsonDbObjectList toDelete;
+ JsonDbObjectList reduces;
+
+ QHash<QString, int> firstNameCount;
+ for (int ii = 0; ii < objects.size(); ii++) {
+ JsonDbObject object(objects.at(ii).toObject());
+ JsonDbWriteResult result = create(mOwner, object);
+ verifyGoodResult(result);
+ firstNameCount[object.value("firstName").toString()]++;
+ toDelete.append(object);
+ }
+
+ objects = readJsonFile(":/daemon/json/reduce.json").toArray();
+ for (int ii = 0; ii < objects.size(); ii++) {
+ JsonDbObject object(objects.at(ii).toObject());
+ if (object.value(JsonDbString::kTypeStr).toString() == JsonDbString::kReduceTypeStr) {
+ object.insert(QLatin1String("add"), object.value(QLatin1String("addFlattened")));
+ object.insert(QLatin1String("subtract"), object.value(QLatin1String("subtractFlattened")));
+ // transitional behavior: null targetValueName indicates whole object is value of the Reduce
+ object.insert(QLatin1String("targetValueName"), QJsonValue(QJsonValue::Null));
+ }
+ JsonDbWriteResult result = create(mOwner, object);
+ verifyGoodResult(result);
+ if (object.value(JsonDbString::kTypeStr).toString() == "Reduce")
+ reduces.append(object);
+ else
+ toDelete.append(object);
+ }
+
+ JsonDbQueryResult queryResult = find(mOwner, QLatin1String("[?_type=\"MyContactCount\"]"));
+ verifyGoodQueryResult(queryResult);
+ QCOMPARE(queryResult.data.size(), firstNameCount.keys().count());
+
+ JsonDbObjectList data = queryResult.data;
+ for (int ii = 0; ii < data.size(); ii++) {
+ QCOMPARE((int)data.at(ii).value("count").toDouble(),
+ firstNameCount[data.at(ii).value("firstName").toString()]);
+ }
+ for (int ii = 0; ii < reduces.size(); ii++)
+ verifyGoodResult(remove(mOwner, reduces.at(ii)));
+ for (int ii = 0; ii < toDelete.size(); ii++)
+ verifyGoodResult(remove(mOwner, toDelete.at(ii)));
+ mJsonDbPartition->removeIndex("MyContactCount");
+}
+
+void TestJsonDb::reduceSourceKeyFunction()
+{
+ QJsonArray objects(readJsonFile(":/daemon/json/reduce-data.json").toArray());
+
+ JsonDbObjectList toDelete;
+ JsonDbObjectList reduces;
+
+ QHash<QString, int> firstNameCount;
+ for (int ii = 0; ii < objects.size(); ii++) {
+ JsonDbObject object(objects.at(ii).toObject());
+ JsonDbWriteResult result = create(mOwner, object);
+ verifyGoodResult(result);
+ firstNameCount[object.value("firstName").toString()]++;
+ toDelete.append(object);
+ }
+
+ objects = readJsonFile(":/daemon/json/reduce.json").toArray();
+ for (int ii = 0; ii < objects.size(); ii++) {
+ JsonDbObject object(objects.at(ii).toObject());
+ if (object.value(JsonDbString::kTypeStr).toString() == JsonDbString::kReduceTypeStr) {
+ QString sourceKeyName = object.value(QLatin1String("sourceKeyName")).toString();
+ object.remove(QLatin1String("sourceKeyName"));
+ object.insert(QLatin1String("sourceKeyFunction"),
+ QString("function (o) { return o.%1; }").arg(sourceKeyName));
+ }
+ JsonDbWriteResult result = create(mOwner, object);
+ verifyGoodResult(result);
+ if (object.value(JsonDbString::kTypeStr).toString() == "Reduce")
+ reduces.append(object);
+ else
+ toDelete.append(object);
+ }
+
+ JsonDbQueryResult queryResult = find(mOwner, QLatin1String("[?_type=\"MyContactCount\"]"));
+ verifyGoodQueryResult(queryResult);
+ QCOMPARE(queryResult.data.size(), firstNameCount.keys().count());
+
+ JsonDbObjectList data = queryResult.data;
+ for (int ii = 0; ii < data.size(); ii++) {
+ QCOMPARE((int)data.at(ii).value("count").toDouble(),
+ firstNameCount[data.at(ii).value("firstName").toString()]);
+ }
+ for (int ii = 0; ii < reduces.size(); ii++)
+ verifyGoodResult(remove(mOwner, reduces.at(ii)));
+ for (int ii = 0; ii < toDelete.size(); ii++)
+ verifyGoodResult(remove(mOwner, toDelete.at(ii)));
+ mJsonDbPartition->removeIndex("MyContactCount");
+}
+
void TestJsonDb::reduceRemoval()
{
QJsonArray objects(readJsonFile(":/daemon/json/reduce-data.json").toArray());
@@ -2776,7 +2918,7 @@ void TestJsonDb::find1()
item.insert("find1", QString("Foobar!"));
create(mOwner, item);
- JsonDbQueryResult queryResult= find(mOwner, QLatin1String(".*"));
+ JsonDbQueryResult queryResult= find(mOwner, QLatin1String("[*]"));
verifyGoodQueryResult(queryResult);
QVERIFY(queryResult.data.size() >= 1);
diff --git a/tests/auto/jsondbqueryobject/testjsondbqueryobject.cpp b/tests/auto/jsondbqueryobject/testjsondbqueryobject.cpp
index f606747..8205a18 100644
--- a/tests/auto/jsondbqueryobject/testjsondbqueryobject.cpp
+++ b/tests/auto/jsondbqueryobject/testjsondbqueryobject.cpp
@@ -67,6 +67,12 @@ const QString qmlProgramForPartition = QLatin1String(
"name: \"com.nokia.shared\";"
"}");
+const QString qmlProgramForQueryWithoutPartition = QLatin1String(
+ "import QtQuick 2.0 \n"
+ "import QtJsonDb 1.0 as JsonDb \n"
+ "JsonDb.Query { "
+ "id:queryObject;"
+ "}");
TestJsonDbQueryObject::TestJsonDbQueryObject()
: mTimedOut(false)
@@ -125,8 +131,11 @@ void TestJsonDbQueryObject::initTestCase()
}
-ComponentData *TestJsonDbQueryObject::createComponent()
+ComponentData *TestJsonDbQueryObject::createComponent(const QString &qml)
{
+ QString tmp = qml;
+ if (tmp.isEmpty())
+ tmp = qmlProgram;
ComponentData *componentData = new ComponentData();
componentData->engine = new QQmlEngine();
QString error;
@@ -136,7 +145,7 @@ ComponentData *TestJsonDbQueryObject::createComponent()
return 0;
}
componentData->component = new QQmlComponent(componentData->engine);
- componentData->component->setData(qmlProgram.toLocal8Bit(), QUrl());
+ componentData->component->setData(tmp.toLocal8Bit(), QUrl());
componentData->qmlElement = componentData->component->create();
if (componentData->component->isError())
qDebug() << componentData->component->errors();
@@ -300,6 +309,43 @@ void TestJsonDbQueryObject::createQuery()
deleteComponent(partition);
}
+void TestJsonDbQueryObject::queryWithoutPartition()
+{
+ ComponentData *queryObject = createComponent(qmlProgramForQueryWithoutPartition);
+ if (!queryObject || !queryObject->qmlElement) return;
+
+ // define index on pos property
+ QVariantMap index;
+ index.insert("_type", "Index");
+ index.insert("propertyName", "pos");
+ index.insert("propertyType", "number");
+ mClient->create(index);
+
+ const QString queryString = QString("[?_type = \""+QString( __FUNCTION__ )+"\"][/pos]");
+ queryObject->qmlElement->setProperty("query", queryString);
+ currentQmlElement = queryObject->qmlElement;
+
+ //Create objects
+ QVariantList items = createObjectList(__FUNCTION__, 10).toList();
+ mClient->create(QVariant(items));
+ qSort(items.begin(), items.end(), posLessThan);
+ const QString expression("start();");
+ QQmlExpression *expr;
+ expr = new QQmlExpression(queryObject->engine->rootContext(), queryObject->qmlElement, expression);
+ expr->evaluate();
+ waitForCallback2();
+ QCOMPARE(callbackError, false);
+ QCOMPARE(cbData.size(), 10);
+ for (int i = 0; i<10; i++) {
+ QVariantMap item = items[i].toMap();
+ QVariantMap obj = cbData[i].toMap();
+ QCOMPARE(obj.value("alphabet"), item.value("alphabet"));
+ }
+
+ delete expr;
+ deleteComponent(queryObject);
+}
+
void TestJsonDbQueryObject::queryBinding()
{
ComponentData *queryObject = createComponent();
diff --git a/tests/auto/jsondbqueryobject/testjsondbqueryobject.h b/tests/auto/jsondbqueryobject/testjsondbqueryobject.h
index 50497a2..c79745a 100644
--- a/tests/auto/jsondbqueryobject/testjsondbqueryobject.h
+++ b/tests/auto/jsondbqueryobject/testjsondbqueryobject.h
@@ -76,6 +76,7 @@ private slots:
void singleObject();
void multipleObjects();
void createQuery();
+ void queryWithoutPartition();
void queryBinding();
void queryError();
void queryLimit();
@@ -88,7 +89,7 @@ protected slots:
void timeout();
private:
- ComponentData *createComponent();
+ ComponentData *createComponent(const QString &qml = QString());
ComponentData *createPartitionComponent();
void deleteComponent(ComponentData *componentData);
diff --git a/tests/auto/qjsondbwatcher/testqjsondbwatcher.cpp b/tests/auto/qjsondbwatcher/testqjsondbwatcher.cpp
index 80be7eb..61d4bc0 100644
--- a/tests/auto/qjsondbwatcher/testqjsondbwatcher.cpp
+++ b/tests/auto/qjsondbwatcher/testqjsondbwatcher.cpp
@@ -95,7 +95,7 @@ public slots:
// from a watcher
void onWatcherNotificationsAvailable(int);
void onWatcherStatusChanged(QtJsonDb::QJsonDbWatcher::Status);
- void onWatcherError(int,QString);
+ void onWatcherError(QtJsonDb::QJsonDbWatcher::ErrorCode,QString);
private:
void removeDbFiles();
@@ -125,6 +125,12 @@ static const char dbfileprefix[] = "test-jsondb-watcher";
mEventLoop.exec(QEventLoop::AllEvents); \
}
+#define waitForWatcherStatus(_watcher, _status) \
+ { \
+ while (_watcher.status() != _status) \
+ mEventLoop.processEvents(QEventLoop::AllEvents); \
+ }
+
TestQJsonDbWatcher::TestQJsonDbWatcher()
: mProcess(0)
{
@@ -183,7 +189,7 @@ void TestQJsonDbWatcher::onWatcherStatusChanged(QtJsonDb::QJsonDbWatcher::Status
}
// this should go into a new version of clientwrapper.h
-void TestQJsonDbWatcher::onWatcherError(int code, QString message)
+void TestQJsonDbWatcher::onWatcherError(QtJsonDb::QJsonDbWatcher::ErrorCode code, QString message)
{
qCritical() << "onWatcherError" << code << message;
mEventLoop.quit();
@@ -256,7 +262,7 @@ void TestQJsonDbWatcher::createAndRemove()
this, SLOT(onWatcherNotificationsAvailable(int)));
connect(&watcher, SIGNAL(statusChanged(QtJsonDb::QJsonDbWatcher::Status)),
this, SLOT(onWatcherStatusChanged(QtJsonDb::QJsonDbWatcher::Status)));
- connect(&watcher, SIGNAL(error(int,QString)), this, SLOT(onWatcherError(int,QString)));
+ connect(&watcher, SIGNAL(error(QtJsonDb::QJsonDbWatcher::ErrorCode,QString)), this, SLOT(onWatcherError(QtJsonDb::QJsonDbWatcher::ErrorCode,QString)));
mConnection->addWatcher(&watcher);
QJsonObject item;
@@ -354,7 +360,7 @@ void TestQJsonDbWatcher::history()
this, SLOT(onWatcherNotificationsAvailable(int)));
connect(&watcher, SIGNAL(statusChanged(QtJsonDb::QJsonDbWatcher::Status)),
this, SLOT(onWatcherStatusChanged(QtJsonDb::QJsonDbWatcher::Status)));
- connect(&watcher, SIGNAL(error(int,QString)), this, SLOT(onWatcherError(int,QString)));
+ connect(&watcher, SIGNAL(error(QtJsonDb::QJsonDbWatcher::ErrorCode,QString)), this, SLOT(onWatcherError(QtJsonDb::QJsonDbWatcher::ErrorCode,QString)));
// set the starting state
watcher.setInitialStateNumber(firstStateNumber-1);
@@ -362,6 +368,7 @@ void TestQJsonDbWatcher::history()
// expecting one notification per create and one state change
waitForResponseAndNotification(0, objects.size()+1);
+ waitForWatcherStatus(watcher, QJsonDbWatcher::Active);
QList<QJsonDbNotification> notifications = watcher.takeNotifications();
QCOMPARE(notifications.size(), objects.size()+1);
@@ -372,6 +379,31 @@ void TestQJsonDbWatcher::history()
QCOMPARE(notifications.at(objects.size()).action(), QJsonDbWatcher::StateChanged);
mConnection->removeWatcher(&watcher);
+
+ // create a new historical watcher that should retrieve all the changes
+ QJsonDbWatcher watcher2;
+ watcher2.setWatchedActions(QJsonDbWatcher::All);
+ watcher2.setQuery(QLatin1String("[?_type=\"com.test.qjsondbwatcher-test\"]"));
+ connect(&watcher2, SIGNAL(notificationsAvailable(int)),
+ this, SLOT(onWatcherNotificationsAvailable(int)));
+ connect(&watcher2, SIGNAL(statusChanged(QtJsonDb::QJsonDbWatcher::Status)),
+ this, SLOT(onWatcherStatusChanged(QtJsonDb::QJsonDbWatcher::Status)));
+ connect(&watcher2, SIGNAL(error(QtJsonDb::QJsonDbWatcher::ErrorCode,QString)), this, SLOT(onWatcherError(QtJsonDb::QJsonDbWatcher::ErrorCode,QString)));
+ watcher2.setInitialStateNumber(-1);
+
+ mConnection->addWatcher(&watcher2);
+ waitForResponseAndNotification(0, objects.size() + 1);
+ waitForWatcherStatus(watcher2, QJsonDbWatcher::Active);
+
+ QList<QJsonDbNotification> notifications2 = watcher2.takeNotifications();
+ QCOMPARE(notifications2.size(), objects.size()+1);
+ // we received one Create notification per object
+ foreach (const QJsonDbNotification n, notifications2.mid(0, objects.size()))
+ QCOMPARE(n.action(), QJsonDbWatcher::Created);
+ // we received one StateChanged notification
+ QCOMPARE(notifications2.at(objects.size()).action(), QJsonDbWatcher::StateChanged);
+
+ mConnection->removeWatcher(&watcher2);
}
void TestQJsonDbWatcher::currentState()
@@ -383,7 +415,7 @@ void TestQJsonDbWatcher::currentState()
this, SLOT(onWatcherNotificationsAvailable(int)));
connect(&watcher, SIGNAL(statusChanged(QtJsonDb::QJsonDbWatcher::Status)),
this, SLOT(onWatcherStatusChanged(QtJsonDb::QJsonDbWatcher::Status)));
- connect(&watcher, SIGNAL(error(int,QString)), this, SLOT(onWatcherError(int,QString)));
+ connect(&watcher, SIGNAL(error(QtJsonDb::QJsonDbWatcher::ErrorCode,QString)), this, SLOT(onWatcherError(QtJsonDb::QJsonDbWatcher::ErrorCode,QString)));
// set the starting state to -1 to get the current state
watcher.setInitialStateNumber(static_cast<quint32>(-1));
diff --git a/tests/auto/queries/dataset.json b/tests/auto/queries/dataset.json
index 8585e44..7697e29 100644
--- a/tests/auto/queries/dataset.json
+++ b/tests/auto/queries/dataset.json
@@ -18,8 +18,6 @@
"num-demon-bunnies":3,
"num-russian-bunnies":2
},
-
-
{
"_type" : "dragon",
"name": "marcia d'arcy",
@@ -151,5 +149,20 @@
"name": "thomas reim",
"type":"russian",
"age": 8
+ },
+ {
+ "_type" : "dog",
+ "name" : "rover",
+ "friends" : ["spike", "reggie"]
+ },
+ {
+ "_type" : "dog",
+ "name" : "spike",
+ "friends" : [ { "name" : "rover", "dog" : true }, { "name" : "puffy", "dog" : false } ]
+ },
+ {
+ "_type" : "dog",
+ "name" : "reggie",
+ "friends" : [ ["larrry", "curly", "moe"], ["spike", "rover"] ]
}
]
diff --git a/tests/auto/queries/testjsondbqueries.cpp b/tests/auto/queries/testjsondbqueries.cpp
index fc2fc55..d5218b6 100644
--- a/tests/auto/queries/testjsondbqueries.cpp
+++ b/tests/auto/queries/testjsondbqueries.cpp
@@ -98,6 +98,8 @@ private slots:
void queryNotEqual();
void queryQuotedProperties();
void querySortedByIndexName();
+ void queryContains();
+ void queryInvalid();
private:
void removeDbFiles();
@@ -311,6 +313,34 @@ void TestJsonDbQueries::queryFieldNotExists()
QCOMPARE(queryResult.data.size(),
mDataStats["num-bunnies"].toInt());
QVERIFY(confirmEachObject(queryResult.data, CheckObjectFieldEqualTo<QString>("_type", "bunny")));
+
+ // verify can use notExists on an indexed field
+ JsonDbObject index;
+ index.insert("_type", QString("Index"));
+ index.insert("propertyName", QString("color"));
+ index.insert("propertyType", QString("string"));
+ verifyGoodWriteResult(mJsonDbPartition->updateObject(mOwner, index));
+
+ // notExists on an indexed field will choose a different sortKey
+ queryResult = find(mOwner, QLatin1String("[?color notExists][? _type = \"bunny\" | _type=\"dragon\"]"));
+ QVERIFY(!queryResult.sortKeys.toArray().contains(QJsonValue(QLatin1String("color"))));
+ QCOMPARE(queryResult.data.size(),
+ mDataStats["num-bunnies"].toInt());
+ QVERIFY(confirmEachObject(queryResult.data, CheckObjectFieldEqualTo<QString>("_type", "bunny")));
+
+ // notExists on an indexed field will choose a different sortKey, even if we try to force it
+ queryResult = find(mOwner, QLatin1String("[?color notExists][? _type = \"bunny\" | _type=\"dragon\"][/color]"));
+ QVERIFY(!queryResult.sortKeys.toArray().contains(QJsonValue(QLatin1String("color"))));
+ QCOMPARE(queryResult.data.size(),
+ mDataStats["num-bunnies"].toInt());
+ QVERIFY(confirmEachObject(queryResult.data, CheckObjectFieldEqualTo<QString>("_type", "bunny")));
+
+ // exists on an indexed field will choose the field as sortKey
+ queryResult = find(mOwner, QLatin1String("[?color exists][? _type = \"bunny\" | _type=\"dragon\"]"));
+ QVERIFY(queryResult.sortKeys.toArray().contains(QJsonValue(QLatin1String("color"))));
+ QCOMPARE(queryResult.data.size(),
+ mDataStats["num-dragons"].toInt());
+ QVERIFY(confirmEachObject(queryResult.data, CheckObjectFieldEqualTo<QString>("_type", "dragon")));
}
void TestJsonDbQueries::queryLessThan()
@@ -398,5 +428,32 @@ void TestJsonDbQueries::querySortedByIndexName()
QVERIFY(confirmEachObject(queryResult.data, CheckSortOrder<double>("age", QList<double>() << 8 << 8 << 6 << 6 << 4 << 4 << 2 << 2 << 0 << 0)));
}
+void TestJsonDbQueries::queryContains()
+{
+ JsonDbQueryResult queryResult = find(mOwner, QLatin1String("[?_type = \"dog\"][?friends contains \"spike\" ]"));
+ QVERIFY(queryResult.error.isNull());
+ QCOMPARE(queryResult.data.count(), 1);
+ queryResult = find(mOwner, QLatin1String("[?_type = \"dog\"][?friends contains { \"name\" : \"puffy\", \"dog\" : false } ]"));
+ QVERIFY(queryResult.error.isNull());
+ QCOMPARE(queryResult.data.count(), 1);
+ queryResult = find(mOwner, QLatin1String("[?_type = \"dog\"][?friends contains [\"spike\", \"rover\"] ]"));
+ QVERIFY(queryResult.error.isNull());
+ QCOMPARE(queryResult.data.count(), 1);
+
+ // invalid queries
+ queryResult = find(mOwner, QLatin1String("[?_type = \"dog\"][?friends contains { \"name\" : \"puffy\" \"dog\" : false } ]"));
+ QVERIFY(!queryResult.error.isNull());
+ queryResult = find(mOwner, QLatin1String("[?_type = \"dog\"][?friends contains [\"spike\", \"rover\" ]"));
+ QVERIFY(!queryResult.error.isNull());
+ queryResult = find(mOwner, QLatin1String("[?_type = \"dog\"][?friends contains \"spike\", \"rover\" ] ]"));
+ QVERIFY(!queryResult.error.isNull());
+}
+
+void TestJsonDbQueries::queryInvalid()
+{
+ JsonDbQueryResult queryResult = find(mOwner, QLatin1String("foo"));
+ QVERIFY(!queryResult.error.isNull());
+}
+
QTEST_MAIN(TestJsonDbQueries)
#include "testjsondbqueries.moc"
diff --git a/tests/shared/util.h b/tests/shared/util.h
index 6301c36..90e6535 100644
--- a/tests/shared/util.h
+++ b/tests/shared/util.h
@@ -77,9 +77,11 @@ QJsonDocument readJsonFile(const QString &filename, QJsonParseError *error = 0)
{
QString filepath = filename;
QFile jsonFile(filepath);
- if (!jsonFile.exists() && error) {
- error->error = QJsonParseError::EndOfString;
- error->offset = 0;
+ if (!jsonFile.exists()) {
+ if (error) {
+ error->error = QJsonParseError::MissingObject;
+ error->offset = 0;
+ }
return QJsonDocument();
}
jsonFile.open(QIODevice::ReadOnly);
diff --git a/tools/aodbread/aodbread.pro b/tools/aodbread/aodbread.pro
index 743ed8e..c8e8563 100644
--- a/tools/aodbread/aodbread.pro
+++ b/tools/aodbread/aodbread.pro
@@ -1,22 +1,13 @@
TARGET = aodbread
-QT = testlib jsondbqson-private
+QT = testlib
CONFIG -= app_bundle
CONFIG += debug
-INCLUDEPATH += $$PWD/../../src/daemon
-LIBS += -L$$QT.jsondb.libs
-
DEFINES += SRCDIR=\\\"$$PWD/\\\"
-#include($$PWD/../../src/daemon/daemon.pri)
include($$PWD/../../src/3rdparty/btree/btree.pri)
-include($$PWD/../../src/3rdparty/qjson/qjson.pri)
-
-HEADERS += \
- $$PWD/../../src/daemon/aodb.h
SOURCES += \
- $$PWD/../../src/daemon/aodb.cpp \
main.cpp
diff --git a/tools/aodbread/main.cpp b/tools/aodbread/main.cpp
index 4b28c71..4c80bcc 100644
--- a/tools/aodbread/main.cpp
+++ b/tools/aodbread/main.cpp
@@ -41,26 +41,21 @@
#include <QtCore>
-#include "aodb.h"
#include "btree.h"
-#include <QtJsonDbQson/private/qson_p.h>
-#include <QtJsonDbQson/private/qsonparser_p.h>
+#include "qbtree.h"
+#include "qbtreetxn.h"
+#include "qbtreecursor.h"
-#include "json.h"
-
-QT_ADDON_JSONDB_USE_NAMESPACE
+#include <stdint.h>
QString printable(const QByteArray &ba)
{
QByteArray array = ba;
- if (ba.startsWith("QSN")) {
- QVariant obj = qsonToVariant(QsonParser::fromRawData(ba));
- JsonWriter writer;
-// writer.setAutoFormatting(true);
- return QString("QSN:") +writer.toString(obj);
- }
+ QJsonDocument doc = QJsonDocument::fromBinaryData(ba);
+ if (!doc.isNull())
+ return QString("QSN:") + doc.toJson();
// check if it is a number
bool isNumber = true;
@@ -71,9 +66,8 @@ QString printable(const QByteArray &ba)
}
}
if (isNumber) {
- for (int i = 0; i < array.size(); ++i) {
+ for (int i = 0; i < array.size(); ++i)
array[i] = array.at(i)+'0';
- }
return QString("N:") +QString::fromLatin1(array);
}
@@ -92,9 +86,8 @@ QString printable(const QByteArray &ba)
}
}
}
- if (utf16) {
+ if (utf16)
return QString("U:") + QString::fromUtf16((const ushort *)array.constData(), array.size()/2);
- }
for (int i = 0; i < array.size(); ++i) {
if (array.at(i) == 0)
@@ -108,9 +101,8 @@ QString printable(const QByteArray &ba)
break;
}
}
- if (isprintable) {
+ if (isprintable)
return QString("S:") + QString::fromLatin1(array);
- }
if (ba.size() == 5 && ba.at(4) == 'S') {
// state change
@@ -150,13 +142,12 @@ bool gDump = false;
bool gWantCompact = false;
bool gShowAll = false;
bool gShowAllReversed = false;
-quint32 gShowState = 0;
qint64 gDumpPage = 0;
void usage()
{
qDebug() << QCoreApplication::arguments().at(0)
- << "[--dump-page num] [--stat] [--dump] [--compact] [--all|--all-reversed] [--state num] database_file";
+ << "[--dump-page num] [--stat] [--dump] [--compact] [--all|--all-reversed] database_file";
}
int main(int argc, char **argv)
@@ -183,13 +174,6 @@ int main(int argc, char **argv)
gShowAll = true;
} else if (arg == QLatin1String("--all-reversed")) {
gShowAllReversed = true;
- } else if (arg == QLatin1String("--state")) {
- bool ok = false;
- gShowState = args.takeFirst().toInt(&ok);
- if (!ok) {
- usage();
- return 0;
- }
} else if (arg == QLatin1String("--dump-page")) {
bool ok = false;
gDumpPage = args.takeFirst().toInt(&ok);
@@ -217,15 +201,17 @@ int main(int argc, char **argv)
return 1;
}
- AoDb db;
- if (!db.open(filename, AoDb::ReadOnly)) {
+ QBtree db;
+ db.setFileName(filename);
+ db.setFlags(QBtree::ReadOnly);
+ if (!db.open()) {
qDebug() << "cannot open" << filename;
usage();
return 0;
}
if (gStat) {
- const struct btree_stat *bs = db.stat();
+ const struct btree_stat *bs = btree_stat(db.handle());
qDebug() << "stat:";
qDebug() << "\thits:" << bs->hits;
qDebug() << "\treads:" << bs->reads;
@@ -241,48 +227,25 @@ int main(int argc, char **argv)
qDebug() << "\ttag:" << bs->tag;
qDebug() << "";
}
+
if (gDump) {
db.dump();
}
- if (gShowState != 0) {
-#if 0
- AoDb statedb;
- QFileInfo fi(filename);
- if (!statedb.open(QString("%1/%2-States.db").arg(fi.dir().path()).arg(fi.baseName()), AoDb::ReadOnly)) {
- qDebug() << "cannot open" << filename;
- usage();
- return 0;
- }
- ObjectCursor cursor(&statedb, &db);
- bool ok = cursor.first(gShowState);
- if (!ok) {
- qDebug() << "Could not seek to" << gShowState;
- return 0;
- }
- QByteArray key, value;
- for (int i = 0; ok && cursor.current(key, value); ok = cursor.next(), ++i) {
- QString keyString, valueString;
- makePrintable(key, keyString, value, valueString);
- qDebug() << i << ":" << keyString << ":" << valueString;
- }
-#endif
- }
-
if (gShowAllReversed) {
- AoDbCursor cursor(&db);
+ QBtreeCursor cursor(&db);
QByteArray key, value;
int i = 0;
- for (bool ok = cursor.last(); ok && cursor.current(key, value); ok = cursor.prev(), ++i) {
+ for (bool ok = cursor.last(); ok && cursor.current(&key, &value); ok = cursor.prev(), ++i) {
QString keyString, valueString;
makePrintable(key, keyString, value, valueString);
qDebug() << i << ":" << keyString << ":" << valueString;
}
} else if (gShowAll) {
- AoDbCursor cursor(&db);
+ QBtreeCursor cursor(&db);
QByteArray key, value;
int i = 0;
- for (bool ok = cursor.first(); ok && cursor.current(key, value); ok = cursor.next(), ++i) {
+ for (bool ok = cursor.first(); ok && cursor.current(&key, &value); ok = cursor.next(), ++i) {
QString keyString, valueString;
makePrintable(key, keyString, value, valueString);
qDebug() << i << ":" << keyString << ":" << valueString;
@@ -291,7 +254,9 @@ int main(int argc, char **argv)
if (gWantCompact) {
db.close();
- db.open(filename);
+ db.setFileName(filename);
+ db.setFlags(QBtree::Default);
+ db.open();
if (db.compact()) {
qDebug() << "compacted.";
} else {
diff --git a/tools/jsondb-client/client.cpp b/tools/jsondb-client/client.cpp
index decc202..aba2bcb 100644
--- a/tools/jsondb-client/client.cpp
+++ b/tools/jsondb-client/client.cpp
@@ -53,8 +53,7 @@
QT_USE_NAMESPACE
-const char* InputThread::commands[] = { "changesSince",
- "create {\"",
+const char* InputThread::commands[] = { "create {\"",
"help",
"load",
"notify create [?",
@@ -274,8 +273,11 @@ void Client::onNotificationsAvailable(int)
case QtJsonDb::QJsonDbWatcher::All: break;
}
- QString message = "Received notification: type " % actionString
- % " for " % watcher->query() % " object:\n" % QJsonDocument(n.object()).toJson();
+ QString message = QString("Received %1 notification for %2 [state %3]\n%4\n")
+ .arg(actionString)
+ .arg(watcher->query())
+ .arg(n.stateNumber())
+ .arg(QString::fromUtf8(QJsonDocument(n.object()).toJson()));
InputThread::print(message);
}
if (!mInputThread)
@@ -333,14 +335,22 @@ void Client::error(QtJsonDb::QJsonDbConnection::ErrorCode error, const QString &
void Client::onRequestFinished()
{
- QtJsonDb::QJsonDbRequest *request = qobject_cast<QtJsonDb::QJsonDbRequest *>(sender());
- Q_ASSERT(request != 0);
- if (!request)
- return;
+ quint32 stateNumber;
+ QtJsonDb::QJsonDbRequest *request = qobject_cast<QtJsonDb::QJsonDbReadRequest*>(sender());
+
+ if (request) {
+ stateNumber = qobject_cast<QtJsonDb::QJsonDbReadRequest*>(request)->stateNumber();
+ } else {
+ request = qobject_cast<QtJsonDb::QJsonDbWriteRequest*>(sender());
+ if (!request)
+ return;
+ stateNumber = qobject_cast<QtJsonDb::QJsonDbWriteRequest*>(request)->stateNumber();
+ }
QList<QJsonObject> objects = request->takeResults();
- QString message = QLatin1String("Received ") + QString::number(objects.size()) + QLatin1String(" object(s):\n");
+ QString message = QString("Received %1 object(s) [state %2]\n").arg(objects.size()).arg(stateNumber);
+
if (!objects.isEmpty()) {
message += QJsonDocument(objects.front()).toJson().trimmed();
for (int i = 1; i < objects.size(); ++i)
@@ -351,6 +361,9 @@ void Client::onRequestFinished()
void Client::aboutToRemove(void)
{
+ // remove the query from the request list
+ mRequests.takeFirst()->deleteLater();
+
QtJsonDb::QJsonDbRequest *queryRequest = qobject_cast<QtJsonDb::QJsonDbRequest *>(sender());
Q_ASSERT(queryRequest != 0);
if (!queryRequest)
@@ -404,8 +417,7 @@ void Client::usage()
<< " remove [partition:<name>] OBJECT" << std::endl
<< " remove [partition:<name>] STRING [limit]" << std::endl
<< " query [partition:<name>] STRING [limit]" << std::endl
- << " notify [partition:<name>] ACTIONS QUERY" << std::endl
- << " changesSince [partition:<name>] STATENUMBER [type1 type2 ...]" << std::endl
+ << " notify [partition:<name>] ACTIONS QUERY [starting-state]" << std::endl
<< " load FILE1 FILE2 ..." << std::endl
<< " help" << std::endl
<< " quit" << std::endl
@@ -419,7 +431,9 @@ void Client::usage()
<< " query [?_type=\"duck\"]" << std::endl
<< " remove {\"_uuid\": \"{18c9d905-5860-464e-a6dd-951464e366de}\", \"_version\": \"1-134f23dbb2\"}" << std::endl
<< " remove [?_type=\"duck\"]" << std::endl
- << " notify create,remove [?_type=\"duck\"]" << std::endl;
+ << " notify create,remove [?_type=\"duck\"]" << std::endl
+ << " notify create,remove [?_type=\"duck\"] 53" << std::endl;
+
QString usageInfo = QString::fromStdString(out.str());
InputThread::print(usageInfo);
@@ -495,18 +509,33 @@ bool Client::processCommand(const QString &command)
else if (s == QLatin1String("update"))
action = QtJsonDb::QJsonDbWatcher::Updated;
if (action == QtJsonDb::QJsonDbWatcher::Action(0)) {
- InputThread::print("uknown notification type" % s);
+ InputThread::print("unknown notification type" % s);
return false;
}
actions |= action;
}
+
+ int startingState = 0;
QString query = rest.mid(s+1).trimmed();
+ if (!query.endsWith(']')) {
+ bool ok;
+ int index = query.lastIndexOf(' ');
+ int state = query.right(query.length() - index).trimmed().toInt(&ok);
+ if (ok)
+ startingState = state;
+ query = query.left(index);
+ }
+
if (mDebug)
qDebug() << "Creating notification:" << alist << ":" << query;
+
QtJsonDb::QJsonDbWatcher *watcher = new QtJsonDb::QJsonDbWatcher(this);
watcher->setPartition(partition);
watcher->setQuery(query);
watcher->setWatchedActions(actions);
+ if (startingState != 0)
+ watcher->setInitialStateNumber(startingState);
+
connect(watcher, SIGNAL(notificationsAvailable(int)), this, SLOT(onNotificationsAvailable(int)));
connect(watcher, SIGNAL(statusChanged(QtJsonDb::QJsonDbWatcher::Status)),
this, SLOT(onNotificationStatusChanged(QtJsonDb::QJsonDbWatcher::Status)));
@@ -558,8 +587,6 @@ bool Client::processCommand(const QString &command)
filenames[i] = filename.mid(1, filename.size()-2);
}
loadFiles(filenames);
- } else if (cmd == "changesSince") {
- qWarning() << "Not yet supported";
} else if (cmd == "connect") {
mConnection->connectToServer();
} else if (cmd == "disconnect") {
@@ -613,12 +640,12 @@ void Client::loadNextFile()
}
QFileInfo info(mFilesToLoad.first());
- if (info.suffix() == QLatin1Literal("json")) {
+ if (info.suffix() == QLatin1String("json")) {
loadJsonFile(info.filePath());
- } else if (info.suffix() == QLatin1Literal("qml")) {
+ } else if (info.suffix() == QLatin1String("qml")) {
loadQmlFile(info.filePath());
#ifndef QTJSONDB_NO_DEPRECATED
- } else if (info.suffix() == QLatin1Literal("js")) {
+ } else if (info.suffix() == QLatin1String("js")) {
loadJavaScriptFile(info.filePath());
#endif
} else {
diff --git a/tools/jsondb-client/jsondbproxy.cpp b/tools/jsondb-client/jsondbproxy.cpp
index 5311220..650f910 100644
--- a/tools/jsondb-client/jsondbproxy.cpp
+++ b/tools/jsondb-client/jsondbproxy.cpp
@@ -67,9 +67,9 @@ QVariantMap _waitForResponse(QtJsonDb::QJsonDbRequest *request) {
res.insert(QLatin1Literal("result"), data);
} else {
QVariantMap error;
- error.insert(QLatin1Literal("code"), 1);
- error.insert(QLatin1Literal("message"), "Error processing request");
- res.insert(QLatin1Literal("error"), error);
+ error.insert(QLatin1String("code"), 1);
+ error.insert(QLatin1String("message"), "Error processing request");
+ res.insert(QLatin1String("error"), error);
}
return res;
}
@@ -84,9 +84,9 @@ JsonDbProxy::JsonDbProxy(QtJsonDb::QJsonDbConnection *conn, QObject *parent) :
QVariantMap JsonDbProxy::find(QVariantMap object)
{
QtJsonDb::QJsonDbReadRequest *request = new QtJsonDb::QJsonDbReadRequest(this);
- request->setQuery(object.value(QLatin1Literal("query")).toString());
- if (object.contains(QLatin1Literal("limit")))
- request->setQueryLimit(object.value(QLatin1Literal("limit")).toInt());
+ request->setQuery(object.value(QLatin1String("query")).toString());
+ if (object.contains(QLatin1String("limit")))
+ request->setQueryLimit(object.value(QLatin1String("limit")).toInt());
mConnection->send(request);
return _waitForResponse(request);
}
@@ -95,8 +95,8 @@ QVariantMap JsonDbProxy::create(QVariantMap object)
{
// handle the to-be-deprecated _id property
QtJsonDb::QJsonDbObject obj = QJsonObject::fromVariantMap(object);
- if (obj.uuid().isNull() && obj.contains(QLatin1Literal("_id"))) {
- obj.setUuid(QtJsonDb::QJsonDbObject::createUuidFromString(obj.value(QLatin1Literal("_id")).toString()));
+ if (obj.uuid().isNull() && obj.contains(QLatin1String("_id"))) {
+ obj.setUuid(QtJsonDb::QJsonDbObject::createUuidFromString(obj.value(QLatin1String("_id")).toString()));
obj.remove(QLatin1String("_id"));
}
QtJsonDb::QJsonDbCreateRequest *request = new QtJsonDb::QJsonDbCreateRequest(QList<QJsonObject>() << obj,
@@ -126,8 +126,8 @@ QVariantMap JsonDbProxy::createList(QVariantList list)
QList<QJsonObject> objects;
foreach (const QVariant &object, list) {
QtJsonDb::QJsonDbObject obj = QJsonObject::fromVariantMap(object.toMap());
- if (!obj.uuid().isNull() && obj.contains(QLatin1Literal("_id"))) {
- obj.setUuid(QtJsonDb::QJsonDbObject::createUuidFromString(obj.value(QLatin1Literal("_id")).toString()));
+ if (!obj.uuid().isNull() && obj.contains(QLatin1String("_id"))) {
+ obj.setUuid(QtJsonDb::QJsonDbObject::createUuidFromString(obj.value(QLatin1String("_id")).toString()));
obj.remove(QLatin1String("_id"));
}
objects << obj;
diff --git a/tools/jsondb-client/main.cpp b/tools/jsondb-client/main.cpp
index 1a1867f..b389131 100644
--- a/tools/jsondb-client/main.cpp
+++ b/tools/jsondb-client/main.cpp
@@ -94,17 +94,17 @@ int main(int argc, char * argv[])
continue;
}
- if (arg == QLatin1Literal("-help")) {
+ if (arg == QLatin1String("-help")) {
usage(progname);
- } else if (arg == QLatin1Literal("-debug")) {
+ } else if (arg == QLatin1String("-debug")) {
debug = true;
- } else if (arg == QLatin1Literal("-load")) {
+ } else if (arg == QLatin1String("-load")) {
if (args.isEmpty()) {
cout << "Must specify a file to load" << endl;
usage(progname, 1);
}
filesToLoad << args.takeFirst();
- } else if (arg == QLatin1Literal("-terminate")) {
+ } else if (arg == QLatin1String("-terminate")) {
terminate = true;
} else {
cout << "Unknown argument " << qPrintable(arg) << endl;