summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJamey Hicks <jamey.hicks@nokia.com>2012-03-26 18:14:57 -0400
committerQt by Nokia <qt-info@nokia.com>2012-04-02 19:20:26 +0200
commit4321c32604c65c9e80ff6cd0b706afdbdd7ec165 (patch)
treec49d2d2ddc56387b371a0c337b41f5f051c56e42
parenta681a2cfa3fbf69891b0455c87e3f141c89e5645 (diff)
Fixes stateNumber on historical notifications
Also, eliminates magic initialStateNumber -1. If state number is specified and is less than current state, then notifications are sent for a summary historical changes. Starting from initial state 0 will yield all changes up to the current state. Fixes bug #7276 and bug #4665 Change-Id: Ica7fcfc5e6513c3854964a107fc81b032f535bec Reviewed-by: Kevin Simons <kevin.simons@nokia.com>
-rw-r--r--src/client/qjsondbconnection.cpp22
-rw-r--r--src/client/qjsondbwatcher.cpp21
-rw-r--r--src/client/qjsondbwatcher.h1
-rw-r--r--src/client/qjsondbwatcher_p.h4
-rw-r--r--src/clientcompat/jsondb-client.cpp2
-rw-r--r--src/daemon/dbserver.cpp159
-rw-r--r--src/daemon/dbserver.h7
-rw-r--r--src/daemon/jsondbnotification.h3
-rw-r--r--src/daemon/jsondbview.cpp77
-rw-r--r--src/daemon/jsondbview.h5
-rw-r--r--tests/auto/qjsondbwatcher/testqjsondbwatcher.cpp86
-rw-r--r--tests/shared/testhelper.cpp43
-rw-r--r--tests/shared/testhelper.h9
-rw-r--r--tools/jsondb-client/client.cpp2
14 files changed, 335 insertions, 106 deletions
diff --git a/src/client/qjsondbconnection.cpp b/src/client/qjsondbconnection.cpp
index ba370572..6c76bbcf 100644
--- a/src/client/qjsondbconnection.cpp
+++ b/src/client/qjsondbconnection.cpp
@@ -300,6 +300,7 @@ void QJsonDbConnectionPrivate::_q_onReceivedObject(const QJsonObject &object)
}
// initialize actionType to silence compiler warnings.
QJsonDbWatcher::Action actionType = QJsonDbWatcher::All;
+ bool stateChanged = false;
if (action == JsonDbStrings::Notification::actionCreate())
actionType = QJsonDbWatcher::Created;
else if (action == JsonDbStrings::Notification::actionUpdate())
@@ -307,10 +308,13 @@ void QJsonDbConnectionPrivate::_q_onReceivedObject(const QJsonObject &object)
else if (action == JsonDbStrings::Notification::actionRemove())
actionType = QJsonDbWatcher::Removed;
else if (action == JsonDbStrings::Notification::actionStateChange())
- actionType = QJsonDbWatcher::StateChanged;
+ stateChanged = true;
else
qWarning() << "Unknown action" << action << "received for notification" << notifyUuid;
- if (actionType != QJsonDbWatcher::All)
+
+ if (stateChanged)
+ watcher->d_func()->handleStateChange(stateNumber);
+ else if (actionType != QJsonDbWatcher::All)
watcher->d_func()->handleNotification(stateNumber, actionType, object);
} else {
// received notification for unknown watcher, just ignore it.
@@ -581,9 +585,15 @@ void QJsonDbConnectionPrivate::initWatcher(QJsonDbWatcher *watcher)
QJsonDbObject object;
object.insert(JsonDbStrings::Property::type(), QJsonValue(JsonDbStrings::Types::notification()));
object.insert(JsonDbStrings::Property::query(), QJsonValue(dwatcher->query));
- // ### TODO: in the future pass initialStateNumber to the server
- object.insert(JsonDbStrings::Property::initialStateNumber(),
- static_cast<int>(qMax(dwatcher->lastStateNumber, dwatcher->initialStateNumber)));
+ bool initialStateNumberSpecified = (dwatcher->initialStateNumber != static_cast<quint32>(QJsonDbWatcherPrivate::UnspecifiedInitialStateNumber));
+ if (dwatcher->lastStateNumber != 0 || initialStateNumberSpecified) {
+ quint32 initialStateNumber;
+ if (initialStateNumberSpecified)
+ initialStateNumber = qMax(dwatcher->lastStateNumber, dwatcher->initialStateNumber);
+ else
+ initialStateNumber = dwatcher->lastStateNumber;
+ object.insert(JsonDbStrings::Property::initialStateNumber(), static_cast<int>(initialStateNumber));
+ }
QJsonArray actions;
if (dwatcher->actions & QJsonDbWatcher::Created)
actions.append(JsonDbStrings::Notification::actionCreate());
@@ -591,8 +601,6 @@ void QJsonDbConnectionPrivate::initWatcher(QJsonDbWatcher *watcher)
actions.append(JsonDbStrings::Notification::actionUpdate());
if (dwatcher->actions & QJsonDbWatcher::Removed)
actions.append(JsonDbStrings::Notification::actionRemove());
- if (dwatcher->actions & QJsonDbWatcher::StateChanged)
- actions.append(JsonDbStrings::Notification::actionStateChange());
object.insert(JsonDbStrings::Property::actions(), actions);
object.insert(JsonDbStrings::Protocol::partition(), QJsonValue(dwatcher->partition));
object.insert(JsonDbStrings::Property::uuid(), QJsonValue(dwatcher->uuid));
diff --git a/src/client/qjsondbwatcher.cpp b/src/client/qjsondbwatcher.cpp
index 77952ea8..2d39abd5 100644
--- a/src/client/qjsondbwatcher.cpp
+++ b/src/client/qjsondbwatcher.cpp
@@ -44,7 +44,6 @@
#include "qjsondbconnection_p.h"
#include <QUuid>
-#include <QDebug>
QT_BEGIN_NAMESPACE_JSONDB
@@ -84,8 +83,6 @@ QJsonDbNotification::QJsonDbNotification(const QJsonObject &object, QJsonDbWatch
longer matches the watcher query string, and the object contains
the property \c{_deleted} with value \c{true}.
- \li If the action() is QJsonDbWatcher::StateChanged, the object is empty.
-
\endlist
\sa QJsonDbObject
@@ -115,7 +112,7 @@ quint32 QJsonDbNotification::stateNumber() const
QJsonDbWatcherPrivate::QJsonDbWatcherPrivate(QJsonDbWatcher *q)
: q_ptr(q), status(QJsonDbWatcher::Inactive),
- actions(QJsonDbWatcher::All), initialStateNumber(0), lastStateNumber(0)
+ actions(QJsonDbWatcher::All), initialStateNumber(QJsonDbWatcherPrivate::UnspecifiedInitialStateNumber), lastStateNumber(0)
{
uuid = QUuid::createUuid().toString();
}
@@ -176,7 +173,6 @@ QJsonDbWatcherPrivate::QJsonDbWatcherPrivate(QJsonDbWatcher *q)
\value Created Watches for objects to start matching the given query string.
\value Updated Watches for modifications of objects matching the given query.
\value Removed Watches for objects that stop matching the given query string.
- \value StateChanged Watches for database state changes.
\value All A convenience value that specifies to watch for all possible actions.
*/
/*!
@@ -339,6 +335,8 @@ quint32 QJsonDbWatcher::initialStateNumber() const
This property contains valid data only after watcher was successfully
activated (i.e. the watcher state was changed to QJsonDbWatcher::Active).
+ The lastStateNumber will be changed after receiving all notifications for that state number.
+
\sa lastStateNumberChanged(), status
*/
quint32 QJsonDbWatcher::lastStateNumber() const
@@ -410,13 +408,20 @@ void QJsonDbWatcherPrivate::handleNotification(quint32 stateNumber, QJsonDbWatch
if (!actions.testFlag(action))
return;
Q_ASSERT(!object.isEmpty());
+ if (initialStateNumber == static_cast<quint32>(QJsonDbWatcherPrivate::UnspecifiedInitialStateNumber))
+ initialStateNumber = stateNumber;
+ QJsonDbNotification n(object, action, stateNumber);
+ notifications.append(n);
+ emit q->notificationsAvailable(notifications.size());
+}
+
+void QJsonDbWatcherPrivate::handleStateChange(quint32 stateNumber)
+{
+ Q_Q(QJsonDbWatcher);
if (stateNumber != lastStateNumber) {
lastStateNumber = stateNumber;
emit q->lastStateNumberChanged(stateNumber);
}
- QJsonDbNotification n(object, action, stateNumber);
- notifications.append(n);
- emit q->notificationsAvailable(notifications.size());
}
/*!
diff --git a/src/client/qjsondbwatcher.h b/src/client/qjsondbwatcher.h
index c2c2e706..0e283418 100644
--- a/src/client/qjsondbwatcher.h
+++ b/src/client/qjsondbwatcher.h
@@ -70,7 +70,6 @@ public:
Created = 0x01, // ### TODO: rename me to StartsMatching ?
Updated = 0x02,
Removed = 0x04,
- StateChanged = 0x08,
All = 0xFF
};
diff --git a/src/client/qjsondbwatcher_p.h b/src/client/qjsondbwatcher_p.h
index c439591e..e156117f 100644
--- a/src/client/qjsondbwatcher_p.h
+++ b/src/client/qjsondbwatcher_p.h
@@ -73,6 +73,7 @@ public:
void _q_onError(QtJsonDb::QJsonDbRequest::ErrorCode code, const QString &message);
void handleNotification(quint32 stateNumber, QJsonDbWatcher::Action action, const QJsonObject &object);
+ void handleStateChange(quint32 stateNumber);
void setStatus(QJsonDbWatcher::Status newStatus);
QJsonDbWatcher *q_ptr;
@@ -81,6 +82,9 @@ public:
QJsonDbWatcher::Actions actions;
QString query;
QString partition;
+ enum {
+ UnspecifiedInitialStateNumber = -1
+ };
quint32 initialStateNumber;
quint32 lastStateNumber;
diff --git a/src/clientcompat/jsondb-client.cpp b/src/clientcompat/jsondb-client.cpp
index 82a117ba..bf4e7939 100644
--- a/src/clientcompat/jsondb-client.cpp
+++ b/src/clientcompat/jsondb-client.cpp
@@ -359,6 +359,8 @@ void JsonDbClientPrivate::_q_handleNotified(const QString &notifyUuid, const QVa
type = JsonDbClient::NotifyUpdate;
} else if (action == JsonDbString::kRemoveStr) {
type = JsonDbClient::NotifyRemove;
+ } else if (action == QLatin1String("stateChange")) {
+ return;
} else {
Q_ASSERT(false);
return;
diff --git a/src/daemon/dbserver.cpp b/src/daemon/dbserver.cpp
index 31b6a8d0..6e3ed81f 100644
--- a/src/daemon/dbserver.cpp
+++ b/src/daemon/dbserver.cpp
@@ -443,8 +443,6 @@ void DBServer::objectsUpdated(const QList<JsonDbUpdate> &objects)
return;
}
- QSet<QString> eagerViewUpdates;
-
// FIXME: pretty good place to batch notifications
foreach (const JsonDbUpdate &updated, objects) {
@@ -470,8 +468,8 @@ void DBServer::objectsUpdated(const QList<JsonDbUpdate> &objects)
// 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);
+ && !partition->findView(objectType))
+ mEagerViewsToUpdate[partitionName].unite(mEagerViewSourceTypes[objectType]);
}
if (object.contains(JsonDbString::kUuidStr))
@@ -492,31 +490,82 @@ void DBServer::objectsUpdated(const QList<JsonDbUpdate> &objects)
}
}
- if (!eagerViewUpdates.isEmpty())
+ if (mEagerViewsToUpdate[partitionName].isEmpty()) {
+ emitStateChanged(partition);
+ } else {
+ mPartitionChanges[partitionName].append(objects);
QMetaObject::invokeMethod(this, "updateEagerViews", Qt::QueuedConnection,
- Q_ARG(JsonDbPartition*, partition),
- Q_ARG(QSet<QString>, eagerViewUpdates));
+ Q_ARG(JsonDbPartition*, partition));
+ }
}
-void DBServer::viewUpdated(const QString &type)
+void DBServer::emitStateChanged(JsonDbPartition *partition)
{
- JsonDbPartition *partition = qobject_cast<JsonDbPartition*>(sender());
if (!partition)
return;
+ quint32 lastStateNumber = partition->mainObjectTable()->stateNumber();
+ QJsonObject stateChange;
+ stateChange.insert("_state", static_cast<int>(lastStateNumber));
+ foreach (const JsonDbNotification *n, mNotificationMap) {
+ if (n->lastStateNumber() == lastStateNumber
+ && n->partition() == partition->name())
+ emit notified(n->uuid(), lastStateNumber, stateChange, "stateChange");
+ }
+}
- if (mEagerViewSourceTypes.contains(type))
- updateEagerViews(partition, QSet<QString>() << type);
+void DBServer::viewUpdated(const QString &type)
+{
+ Q_UNUSED(type);
}
-void DBServer::updateEagerViews(JsonDbPartition *partition, const QSet<QString> &viewTypes)
+void DBServer::updateEagerViews(JsonDbPartition *partition)
{
- 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);
+ if (!partition)
+ return;
+ QSet<QString> viewTypes = mEagerViewsToUpdate[partition->name()];
+ if (viewTypes.isEmpty())
+ return;
+ const QString &partitionName = partition->name();
+ QSet<QString> viewsUpdated;
+ if (jsondbSettings->verbose())
+ qDebug() << "updateEagerViews {" << partition->mainObjectTable()->stateNumber() << viewTypes;
+ while (!viewTypes.isEmpty()) {
+ bool madeProgress = false;
+ foreach (const QString &targetType, viewTypes) {
+ JsonDbView *view = partition->findView(targetType);
+ if (!view) {
+ qWarning() << "non-view viewType?" << targetType << "eager views to update" << mEagerViewsToUpdate[partitionName];
+ continue;
+ }
+ QSet<QString> typesNeeded(view->sourceTypeSet());
+ typesNeeded.intersect(viewTypes);
+ if (!typesNeeded.isEmpty())
+ continue;
+ viewTypes.remove(targetType);
+ view->updateEagerView(mPartitionChanges[partitionName]);
+ viewsUpdated.insert(targetType);
+ // if this triggers other eager types, we need to update that also
+ foreach (const QString viewType, mEagerViewSourceTypes[targetType]) {
+ if (viewsUpdated.contains(viewType))
+ qWarning() << "View update cycle detected" << targetType << viewType << viewsUpdated;
+ else
+ viewTypes.insert(viewType);
+ }
+ viewTypes.unite(mEagerViewSourceTypes[targetType]);
+
+ madeProgress = true;
+ }
+ if (!madeProgress) {
+ qCritical() << "Failed to update any views" << viewTypes;
+ break;
}
}
+ mEagerViewsToUpdate[partition->name()].clear();
+
+ emitStateChanged(partition);
+
+ if (jsondbSettings->verbose())
+ qDebug() << "updateEagerViews }" << partition->mainObjectTable()->stateNumber();
}
void DBServer::objectUpdated(const QString &partitionName, quint32 stateNumber, JsonDbNotification *n,
@@ -554,6 +603,7 @@ void DBServer::objectUpdated(const QString &partitionName, quint32 stateNumber,
(effectiveAction == JsonDbNotification::Update ? JsonDbString::kUpdateStr :
JsonDbString::kRemoveStr));
notified(n->uuid(), stateNumber, r, actionStr);
+ n->setLastStateNumber(stateNumber);
}
}
}
@@ -672,6 +722,7 @@ void DBServer::processRead(JsonStream *stream, JsonDbOwner *owner, const QJsonVa
void DBServer::processChangesSince(JsonStream *stream, JsonDbOwner *owner, const QJsonValue &object, const QString &partitionName, int id)
{
+ Q_UNUSED(owner);
QJsonObject result;
if (object.type() == QJsonValue::Object) {
@@ -697,6 +748,7 @@ void DBServer::processChangesSince(JsonStream *stream, JsonDbOwner *owner, const
void DBServer::processFlush(JsonStream *stream, JsonDbOwner *owner, const QString &partitionName, int id)
{
+ Q_UNUSED(owner);
JsonDbPartition *partition = mPartitions.value(partitionName, mDefaultPartition);
QJsonObject result = partition->flush();
result.insert(JsonDbString::kIdStr, id);
@@ -792,18 +844,20 @@ void DBServer::createNotification(const JsonDbObject &object, JsonStream *stream
QStringList actions = QVariant(object.value(JsonDbString::kActionsStr).toArray().toVariantList()).toStringList();
QString query = object.value(JsonDbString::kQueryStr).toString();
QJsonObject bindings = object.value("bindings").toObject();
- QString partition = object.value(JsonDbString::kPartitionStr).toString();
-
- bool ok;
- quint32 stateNumber = object.value("initialStateNumber").toVariant().toInt(&ok);
- if (!ok)
- stateNumber = 0;
+ QString partitionName = object.value(JsonDbString::kPartitionStr).toString();
+ quint32 stateNumber = 0;
- if (partition.isEmpty())
- partition = mDefaultPartition->name();
+ if (partitionName.isEmpty())
+ partitionName = mDefaultPartition->name();
+ JsonDbPartition *partition = findPartition(partitionName);
- JsonDbNotification *n = new JsonDbNotification(getOwner(stream), uuid, query, actions, partition);
+ JsonDbNotification *n = new JsonDbNotification(getOwner(stream), uuid, query, actions, partitionName);
+ if (object.contains("initialStateNumber") && object.value("initialStateNumber").isDouble())
+ stateNumber = static_cast<quint32>(object.value("initialStateNumber").toDouble());
+ else if (partition)
+ stateNumber = partition->mainObjectTable()->stateNumber();
n->setInitialStateNumber(stateNumber);
+
JsonDbQuery *parsedQuery = JsonDbQuery::parse(query, bindings);
n->setCompiledQuery(parsedQuery);
const QList<OrQueryTerm> &orQueryTerms = parsedQuery->queryTerms;
@@ -835,11 +889,10 @@ void DBServer::createNotification(const JsonDbObject &object, JsonStream *stream
mNotifications[uuid] = stream;
foreach (const QString &objectType, parsedQuery->matchedTypes())
- updateEagerViewTypes(objectType, mPartitions.value(partition, mDefaultPartition), stateNumber);
+ updateEagerViewTypes(objectType, mPartitions.value(partitionName, mDefaultPartition), stateNumber);
- if (stateNumber)
+ if (partition)
notifyHistoricalChanges(n);
-
}
void DBServer::removeNotification(const JsonDbObject &object)
@@ -880,7 +933,7 @@ void DBServer::notifyHistoricalChanges(JsonDbNotification *n)
JsonDbQuery *parsedQuery = n->parsedQuery();
QSet<QString> matchedTypes = parsedQuery->matchedTypes();
bool matchAnyType = matchedTypes.isEmpty();
- if (stateNumber == static_cast<quint32>(-1)) {
+ if (stateNumber == 0) {
QString indexName = JsonDbString::kTypeStr;
if (matchAnyType) {
matchedTypes.insert(QString());
@@ -889,6 +942,9 @@ void DBServer::notifyHistoricalChanges(JsonDbNotification *n)
}
foreach (const QString matchedType, matchedTypes) {
JsonDbObjectTable *objectTable = partition->findObjectTable(matchedType);
+ lastStateNumber = objectTable->stateNumber();
+ if (lastStateNumber == stateNumber)
+ continue;
// views dont have a _type index
if (partition->findView(matchedType))
@@ -904,33 +960,40 @@ void DBServer::notifyHistoricalChanges(JsonDbNotification *n)
}
JsonDbObject oldObject;
+ int c = 0;
for (JsonDbObject o = indexQuery.data()->first(); !o.isEmpty(); o = indexQuery.data()->next()) {
JsonDbNotification::Action action = JsonDbNotification::Create;
- objectUpdated(partition->name(), stateNumber, n, action, oldObject, o);
+ objectUpdated(partition->name(), lastStateNumber, n, action, oldObject, o);
+ c++;
}
}
} else {
- QJsonObject changesSince = partition->changesSince(stateNumber, matchedTypes);
- QJsonObject changes(changesSince.value("result").toObject());
- lastStateNumber = changes.value("currentStateNumber").toDouble();
- QJsonArray changeList(changes.value("changes").toArray());
- quint32 count = changeList.size();
- for (quint32 i = 0; i < count; i++) {
- QJsonObject change = changeList.at(i).toObject();
- QJsonObject before = change.value("before").toObject();
- QJsonObject after = change.value("after").toObject();
-
- JsonDbNotification::Action action = JsonDbNotification::Update;
- if (before.isEmpty())
- action = JsonDbNotification::Create;
- else if (after.contains(JsonDbString::kDeletedStr))
- action = JsonDbNotification::Delete;
- objectUpdated(partition->name(), stateNumber, n, action, before, after);
+ foreach (const QString matchedType, matchedTypes) {
+ JsonDbObjectTable *objectTable = partition->findObjectTable(matchedType);
+ if (objectTable->stateNumber() == stateNumber)
+ continue;
+ QJsonObject changesSince = objectTable->changesSince(stateNumber, matchedTypes);
+ QJsonObject changes(changesSince.value("result").toObject());
+ lastStateNumber = changes.value("currentStateNumber").toDouble();
+ QJsonArray changeList(changes.value("changes").toArray());
+ quint32 count = changeList.size();
+ for (quint32 i = 0; i < count; i++) {
+ QJsonObject change = changeList.at(i).toObject();
+ QJsonObject before = change.value("before").toObject();
+ QJsonObject after = change.value("after").toObject();
+
+ JsonDbNotification::Action action = JsonDbNotification::Update;
+ if (before.isEmpty())
+ action = JsonDbNotification::Create;
+ else if (after.contains(JsonDbString::kDeletedStr))
+ action = JsonDbNotification::Delete;
+ objectUpdated(partition->name(), lastStateNumber, n, action, before, after);
+ }
}
}
QJsonObject stateChange;
stateChange.insert("_state", static_cast<int>(lastStateNumber));
- emit notified(n->uuid(), stateNumber, stateChange, "stateChange");
+ emit notified(n->uuid(), lastStateNumber, stateChange, "stateChange");
}
void DBServer::updateEagerViewTypes(const QString &objectType, JsonDbPartition *partition, quint32 stateNumber)
diff --git a/src/daemon/dbserver.h b/src/daemon/dbserver.h
index 159a6f90..a4c0b49c 100644
--- a/src/daemon/dbserver.h
+++ b/src/daemon/dbserver.h
@@ -95,7 +95,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);
+ void updateEagerViews(JsonDbPartition *partition);
+ void emitStateChanged(JsonDbPartition *partition);
private:
void objectUpdated(const QString &partitionName, quint32 stateNumber, JsonDbNotification *n, JsonDbNotification::Action action, const JsonDbObject &oldObject, const JsonDbObject &object);
@@ -127,7 +128,9 @@ private:
JsonDbEphemeralPartition *mEphemeralPartition;
QMap<QString, JsonDbNotification *> mNotificationMap;
QMultiMap<QString, JsonDbNotification *> mKeyedNotifications;
- QMap<QString,QSet<QString> > mEagerViewSourceTypes; // set of eager view types dependent on this source type
+ QMap<QString,QSet<QString> > mEagerViewSourceTypes; // set of eager view types dependent on this source type
+ QMap<QString,QSet<QString> > mEagerViewsToUpdate; // by partitionName, used by updateEagerViews();
+ QMap<QString, JsonDbUpdateList> mPartitionChanges; // by partitionName
quint16 mTcpServerPort;
QLocalServer *mServer;
QTcpServer *mTcpServer;
diff --git a/src/daemon/jsondbnotification.h b/src/daemon/jsondbnotification.h
index b3de21df..e02e8a30 100644
--- a/src/daemon/jsondbnotification.h
+++ b/src/daemon/jsondbnotification.h
@@ -70,6 +70,8 @@ public:
const QString & partition() const { return mPartition; }
quint32 initialStateNumber() const { return mInitialStateNumber; }
void setInitialStateNumber(quint32 stateNumber) { mInitialStateNumber = stateNumber; }
+ quint32 lastStateNumber() const { return mLastStateNumber; }
+ void setLastStateNumber(quint32 stateNumber) { mLastStateNumber = stateNumber; }
private:
const JsonDbOwner *mOwner;
QString mUuid;
@@ -78,6 +80,7 @@ private:
Actions mActions;
QString mPartition;
quint32 mInitialStateNumber;
+ quint32 mLastStateNumber;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(JsonDbNotification::Actions)
diff --git a/src/daemon/jsondbview.cpp b/src/daemon/jsondbview.cpp
index 1a229d29..9b689e5f 100644
--- a/src/daemon/jsondbview.cpp
+++ b/src/daemon/jsondbview.cpp
@@ -325,6 +325,8 @@ void JsonDbView::updateView(quint32 desiredStateNumber)
// up-to-date with respect this source table
continue;
+ // TODO: fix changesSince to return JsonDbUpdateList
+ // and then call JsonDbView::updateViewOnChanges() here
QJsonObject changesSince(sourceTable->changesSince(viewStateNumber, sourceTypes));
QJsonObject changes(changesSince.value("result").toObject());
QJsonArray changeList(changes.value("changes").toArray());
@@ -376,6 +378,81 @@ void JsonDbView::updateView(quint32 desiredStateNumber)
emit updated(mViewType);
}
+void JsonDbView::updateEagerView(const JsonDbUpdateList &objectsUpdated)
+{
+ quint32 partitionStateNumber = mMainObjectTable->stateNumber();
+ quint32 viewStateNumber = mViewObjectTable->stateNumber();
+
+ // make sure we can run this set of updates
+ if (viewStateNumber != (partitionStateNumber-1)
+ || viewDefinitionUpdated(objectsUpdated))
+ // otherwise do a full update
+ updateView(partitionStateNumber);
+
+ // begin transaction
+ mViewObjectTable->begin();
+
+ // then do the update
+ QSet<QString> processedDefinitionUuids;
+ updateViewOnChanges(objectsUpdated, processedDefinitionUuids);
+
+ // end transaction
+ JsonDbScriptEngine::scriptEngine()->collectGarbage();
+ mViewObjectTable->commit(partitionStateNumber);
+}
+
+bool JsonDbView::viewDefinitionUpdated(const JsonDbUpdateList &objectsUpdated) const
+{
+ foreach (const JsonDbUpdate &update, objectsUpdated) {
+ QJsonObject beforeObject = update.oldObject;
+ QJsonObject afterObject = update.newObject;
+ QString beforeUuid = beforeObject.value(JsonDbString::kUuidStr).toString();
+ QString afterUuid = afterObject.value(JsonDbString::kUuidStr).toString();
+
+ if ((!beforeObject.isEmpty()
+ && (mMapDefinitions.contains(beforeUuid) || mReduceDefinitions.contains(beforeUuid)))
+ || (!afterObject.isEmpty()
+ && (mMapDefinitions.contains(afterUuid) || mReduceDefinitions.contains(afterUuid))))
+ return false;
+ }
+ return true;
+}
+
+void JsonDbView::updateViewOnChanges(const JsonDbUpdateList &objectsUpdated,
+ QSet<QString> &processedDefinitionUuids)
+{
+ foreach (const JsonDbUpdate &update, objectsUpdated) {
+ QJsonObject beforeObject = update.oldObject;
+ QJsonObject afterObject = update.newObject;
+ QString beforeType = beforeObject.value(JsonDbString::kTypeStr).toString();
+ QString afterType = afterObject.value(JsonDbString::kTypeStr).toString();
+
+ if (mMapDefinitionsBySource.contains(beforeType)) {
+ JsonDbMapDefinition *def = mMapDefinitionsBySource.value(beforeType);
+ if (processedDefinitionUuids.contains(def->uuid()))
+ continue;
+ def->updateObject(beforeObject, afterObject);
+ } else if (mMapDefinitionsBySource.contains(afterType)) {
+ JsonDbMapDefinition *def = mMapDefinitionsBySource.value(afterType);
+ if (processedDefinitionUuids.contains(def->uuid()))
+ continue;
+ def->updateObject(beforeObject, afterObject);
+ }
+
+ if (mReduceDefinitionsBySource.contains(beforeType)) {
+ JsonDbReduceDefinition *def = mReduceDefinitionsBySource.value(beforeType);
+ if (processedDefinitionUuids.contains(def->uuid()))
+ continue;
+ def->updateObject(beforeObject, afterObject);
+ } else if (mReduceDefinitionsBySource.contains(afterType)) {
+ JsonDbReduceDefinition *def = mReduceDefinitionsBySource.value(afterType);
+ if (processedDefinitionUuids.contains(def->uuid()))
+ continue;
+ def->updateObject(beforeObject, afterObject);
+ }
+ }
+}
+
bool JsonDbView::processUpdatedDefinitions(const QString &viewType, quint32 targetStateNumber,
QSet<QString> &processedDefinitionUuids)
{
diff --git a/src/daemon/jsondbview.h b/src/daemon/jsondbview.h
index a2d56756..55afdbfa 100644
--- a/src/daemon/jsondbview.h
+++ b/src/daemon/jsondbview.h
@@ -67,6 +67,7 @@ public:
JsonDbPartition *partition() const { return mPartition; }
JsonDbObjectTable *objectTable() const { return mViewObjectTable; }
QStringList sourceTypes() const { return mSourceTypes; }
+ QSet<QString> sourceTypeSet() const { return QSet<QString>::fromList(mSourceTypes); }
void open();
void close();
@@ -76,6 +77,7 @@ public:
static void removeDefinition(JsonDbPartition *partition, QJsonObject definition);
void updateView(quint32 stateNumber=0);
+ void updateEagerView(const JsonDbUpdateList &objectsUpdated);
void reduceMemoryUsage();
bool isActive() const;
@@ -91,6 +93,9 @@ private:
bool processUpdatedDefinitions(const QString &viewType, quint32 targetStateNumber,
QSet<QString> &processedDefinitions);
void updateSourceTypesList();
+ bool viewDefinitionUpdated(const JsonDbUpdateList &objectsUpdated) const;
+ void updateViewOnChanges(const JsonDbUpdateList &objectsUpdated, QSet<QString> &processedDefinitionUuids);
+
private:
JsonDbPartition *mPartition;
JsonDbObjectTable *mViewObjectTable; // view object table
diff --git a/tests/auto/qjsondbwatcher/testqjsondbwatcher.cpp b/tests/auto/qjsondbwatcher/testqjsondbwatcher.cpp
index 1e2cd201..a614717b 100644
--- a/tests/auto/qjsondbwatcher/testqjsondbwatcher.cpp
+++ b/tests/auto/qjsondbwatcher/testqjsondbwatcher.cpp
@@ -145,6 +145,7 @@ void TestQJsonDbWatcher::createAndRemove()
watcher.setQuery(QLatin1String("[?_type=\"com.test.qjsondbwatcher-test\"]"));
watcher.setPartition(partition);
mConnection->addWatcher(&watcher);
+ waitForStatus(&watcher, QJsonDbWatcher::Active);
QJsonObject item;
item.insert(JsonDbStrings::Property::type(), QLatin1String("com.test.qjsondbwatcher-test"));
@@ -210,7 +211,6 @@ void TestQJsonDbWatcher::history()
for (int i = 0; i < qMin(100, array.size()); i++) {
QJsonObject item = array.at(i).toObject();
- // why does QJsonDbCreate request require me to set the Uuid? /me thinks it's a bug
item.insert(JsonDbStrings::Property::uuid(), QUuid::createUuid().toString());
item.insert(JsonDbStrings::Property::type(), QLatin1String("com.test.qjsondbwatcher-test"));
objects.append(item);
@@ -234,17 +234,17 @@ void TestQJsonDbWatcher::history()
watcher.setInitialStateNumber(firstStateNumber-1);
mConnection->addWatcher(&watcher);
- // expecting one notification per create and one state change
- waitForResponseAndNotifications(0, &watcher, objects.size()+1);
+ // expecting one notification per create
+ waitForResponseAndNotifications(0, &watcher, objects.size());
waitForStatus(&watcher, QJsonDbWatcher::Active);
QList<QJsonDbNotification> notifications = watcher.takeNotifications();
- QCOMPARE(notifications.size(), objects.size()+1);
+ QCOMPARE(notifications.size(), objects.size());
// we received one Create notification per object
- foreach (const QJsonDbNotification n, notifications.mid(0, objects.size()))
+ foreach (const QJsonDbNotification n, notifications.mid(0, objects.size())) {
QCOMPARE(n.action(), QJsonDbWatcher::Created);
- // we received one StateChanged notification
- QCOMPARE(notifications.at(objects.size()).action(), QJsonDbWatcher::StateChanged);
+ QVERIFY(n.stateNumber() >= firstStateNumber);
+ }
mConnection->removeWatcher(&watcher);
@@ -252,20 +252,28 @@ void TestQJsonDbWatcher::history()
QJsonDbWatcher watcher2;
watcher2.setWatchedActions(QJsonDbWatcher::All);
watcher2.setQuery(QLatin1String("[?_type=\"com.test.qjsondbwatcher-test\"]"));
- watcher2.setInitialStateNumber(-1);
+ watcher2.setInitialStateNumber(0);
mConnection->addWatcher(&watcher2);
- waitForResponseAndNotifications(0, &watcher2, objects.size() + 1);
- waitForStatus(&watcher2, QJsonDbWatcher::Active);
+ waitForResponseAndNotifications(0, &watcher2, objects.size());
QList<QJsonDbNotification> notifications2 = watcher2.takeNotifications();
- QCOMPARE(notifications2.size(), objects.size()+1);
+ QCOMPARE(notifications2.size(), objects.size());
// 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);
+ // create another one
+ {
+ QJsonObject item;
+ item.insert(JsonDbStrings::Property::uuid(), QUuid::createUuid().toString());
+ item.insert(JsonDbStrings::Property::type(), QLatin1String("com.test.qjsondbwatcher-test"));
+ item.insert("another one", true);
+ QJsonDbCreateRequest request(item);
+ mConnection->send(&request);
+ // wait for response from request, one create notification
+ waitForResponseAndNotifications(&request, &watcher2, 1);
+ }
mConnection->removeWatcher(&watcher2);
QJsonDbRemoveRequest remove(objects);
@@ -273,26 +281,20 @@ void TestQJsonDbWatcher::history()
waitForResponse(&remove);
}
+
void TestQJsonDbWatcher::currentState()
{
QJsonDbWatcher watcher;
watcher.setWatchedActions(QJsonDbWatcher::All);
watcher.setQuery(QLatin1String("[?_type=\"com.test.qjsondbwatcher-test\"]"));
- // set the starting state to -1 to get the current state
- watcher.setInitialStateNumber(static_cast<quint32>(-1));
+ // set the starting state to 0 to get the current state
+ watcher.setInitialStateNumber(0);
mConnection->addWatcher(&watcher);
- // expecting one notification per create and one state change
- bool stateChanged = false;
- while (!stateChanged) {
- // wait for a notification
- waitForResponseAndNotifications(0, &watcher, 1);
- QList<QJsonDbNotification> notifications = watcher.takeNotifications();
- foreach (const QJsonDbNotification n, notifications)
- if (n.action() == QJsonDbWatcher::StateChanged)
- stateChanged = true;
- }
+ // expecting one notification per create
+ waitForResponseAndNotifications(0, &watcher, 1, 1);
+ watcher.takeNotifications();
// now create another object
QJsonObject item;
@@ -318,6 +320,7 @@ void TestQJsonDbWatcher::notificationTriggersView()
{
QVERIFY(mConnection);
+ QLatin1String query("[?_type=\"com.test.TestView\"]");
QJsonArray array(readJsonFile(":/daemon/json/map-array-conversion.json").array());
QList<QJsonObject> objects;
foreach (const QJsonValue v, array)
@@ -331,21 +334,36 @@ void TestQJsonDbWatcher::notificationTriggersView()
mConnection->send(&request);
waitForResponse(&request);
+ {
+ QJsonDbReadRequest read(query);
+ mConnection->send(&read);
+ waitForResponse(&read);
+ QList<QJsonObject> objects = read.takeResults();
+ int numObjects = objects.size();
+ QCOMPARE(numObjects, 1);
+ }
+
// create a watcher
QJsonDbWatcher watcher;
watcher.setWatchedActions(QJsonDbWatcher::All);
- watcher.setQuery(QLatin1String("[?_type=\"com.test.TestView\"]"));
+ watcher.setQuery(QLatin1String(query));
mConnection->addWatcher(&watcher);
- waitForResponseAndNotifications(0, &watcher, 1);
waitForStatus(&watcher, QJsonDbWatcher::Active);
- QList<QJsonDbNotification> notifications = watcher.takeNotifications();
- QCOMPARE(notifications.size(), 1);
- QJsonDbNotification n = notifications[0];
- QJsonObject o = n.object();
- // make sure we got notified on the right object
- //QCOMPARE(o.value(JsonDbStrings::Property::uuid()), info.value(JsonDbStrings::Property::uuid()));
-
+ {
+ QJsonObject item;
+ item.insert(JsonDbStrings::Property::type(), QLatin1String("com.test.Test"));
+ QJsonDbCreateRequest request(item);
+ mConnection->send(&request);
+ waitForResponseAndNotifications(&request, &watcher, 1);
+
+ QList<QJsonDbNotification> notifications = watcher.takeNotifications();
+ QCOMPARE(notifications.size(), 1);
+ QJsonDbNotification n = notifications[0];
+ QJsonObject o = n.object();
+ // make sure we got notified on the right object
+ //QCOMPARE(o.value(JsonDbStrings::Property::uuid()), info.value(JsonDbStrings::Property::uuid()));
+ }
mConnection->removeWatcher(&watcher);
QList<QJsonObject> toDelete;
diff --git a/tests/shared/testhelper.cpp b/tests/shared/testhelper.cpp
index b67c8002..e630ffa0 100644
--- a/tests/shared/testhelper.cpp
+++ b/tests/shared/testhelper.cpp
@@ -59,6 +59,8 @@ TestHelper::TestHelper(QObject *parent) :
, mConnection(0)
, mNotificationsReceived(0)
, mNotificationsExpected(0)
+ , mLastStateChangedExpected(0)
+ , mLastStateChangedReceived(0)
, mRequestsPending(0)
{
}
@@ -91,6 +93,8 @@ void TestHelper::launchJsonDbDaemon(const QString &basename, const QStringList &
mProcess = new QProcess;
mProcess->setProcessChannelMode(QProcess::ForwardedChannels);
+ connect(mProcess, SIGNAL(finished(int,QProcess::ExitStatus)),
+ this, SLOT(processFinished(int,QProcess::ExitStatus)));
QString socketName = QString("testjsondb_%1").arg(getpid());
@@ -222,6 +226,8 @@ void TestHelper::removeDbFiles(const QStringList &additionalFiles)
void TestHelper::waitForResponse(QJsonDbRequest *request)
{
mRequestsPending = 1;
+ mNotificationsExpected = 0;
+ mLastStateChangedExpected = 0;
connect(request, SIGNAL(finished()), this, SLOT(requestFinished()));
connect(request, SIGNAL(error(QtJsonDb::QJsonDbRequest::ErrorCode,QString)),
@@ -232,11 +238,14 @@ void TestHelper::waitForResponse(QJsonDbRequest *request)
disconnect(request, SIGNAL(finished()), this, SLOT(requestFinished()));
disconnect(request, SIGNAL(error(QtJsonDb::QJsonDbRequest::ErrorCode,QString)),
this, SLOT(requestError(QtJsonDb::QJsonDbRequest::ErrorCode,QString)));
+ QVERIFY(!mRequestsPending);
}
void TestHelper::waitForResponse(QList<QJsonDbRequest *> requests)
{
mRequestsPending = requests.count();
+ mNotificationsExpected = 0;
+ mLastStateChangedExpected = 0;
foreach (QJsonDbRequest *request, requests) {
connect(request, SIGNAL(finished()), this, SLOT(requestFinished()));
@@ -251,14 +260,18 @@ void TestHelper::waitForResponse(QList<QJsonDbRequest *> requests)
disconnect(request, SIGNAL(error(QtJsonDb::QJsonDbRequest::ErrorCode,QString)),
this, SLOT(requestError(QtJsonDb::QJsonDbRequest::ErrorCode,QString)));
}
+ QVERIFY(!mRequestsPending);
}
void TestHelper::waitForResponseAndNotifications(QJsonDbRequest *request,
QJsonDbWatcher *watcher,
- int notificationsExpected)
+ int notificationsExpected,
+ int lastStateChangedExpected)
{
mNotificationsExpected = notificationsExpected;
mNotificationsReceived = 0;
+ mLastStateChangedExpected = lastStateChangedExpected;
+ mLastStateChangedReceived = 0;
if (request) {
mRequestsPending = 1;
@@ -272,6 +285,8 @@ void TestHelper::waitForResponseAndNotifications(QJsonDbRequest *request,
this, SLOT(watcherNotificationsAvailable(int)));
connect(watcher, SIGNAL(error(QtJsonDb::QJsonDbWatcher::ErrorCode,QString)),
this, SLOT(watcherError(QtJsonDb::QJsonDbWatcher::ErrorCode,QString)));
+ connect(watcher, SIGNAL(lastStateNumberChanged(int)),
+ this, SLOT(watcherLastStateNumberChanged(int)));
blockWithTimeout();
@@ -285,6 +300,11 @@ void TestHelper::waitForResponseAndNotifications(QJsonDbRequest *request,
this, SLOT(watcherNotificationsAvailable(int)));
disconnect(watcher, SIGNAL(error(QtJsonDb::QJsonDbWatcher::ErrorCode,QString)),
this, SLOT(watcherError(QtJsonDb::QJsonDbWatcher::ErrorCode,QString)));
+ disconnect(watcher, SIGNAL(lastStateNumberChanged(int)),
+ this, SLOT(watcherLastStateNumberChanged(int)));
+
+ QVERIFY(!mRequestsPending && mNotificationsReceived >= mNotificationsExpected
+ && mLastStateChangedReceived >= mLastStateChangedExpected);
mNotificationsExpected = 0;
}
@@ -297,6 +317,7 @@ void TestHelper::waitForStatus(QJsonDbWatcher *watcher, QJsonDbWatcher::Status s
blockWithTimeout();
disconnect(watcher, SIGNAL(statusChanged(QtJsonDb::QJsonDbWatcher::Status)),
this, SLOT(watcherStatusChanged(QtJsonDb::QJsonDbWatcher::Status)));
+ QVERIFY(mExpectedStatus == status);
}
bool TestHelper::dontLaunch()
@@ -327,10 +348,16 @@ void TestHelper::connectionError(QtJsonDb::QJsonDbConnection::ErrorCode code, QS
qCritical() << "Error from connection" << code << msg;
}
+void TestHelper::processFinished(int code, QProcess::ExitStatus status)
+{
+ qDebug() << "jsondb process finished" << code << status;
+}
+
void TestHelper::requestFinished()
{
--mRequestsPending;
- if (!mRequestsPending && mNotificationsReceived >= mNotificationsExpected)
+ if (!mRequestsPending && mNotificationsReceived >= mNotificationsExpected
+ && mLastStateChangedReceived >= mLastStateChangedExpected)
mEventLoop.quit();
}
@@ -349,7 +376,8 @@ void TestHelper::watcherNotificationsAvailable(int count)
{
mNotificationsReceived = count;
- if (!mRequestsPending && mNotificationsReceived >= mNotificationsExpected)
+ if (!mRequestsPending && mNotificationsReceived >= mNotificationsExpected
+ && mLastStateChangedReceived >= mLastStateChangedExpected)
mEventLoop.quit();
}
@@ -359,6 +387,15 @@ void TestHelper::watcherStatusChanged(QtJsonDb::QJsonDbWatcher::Status status)
mEventLoop.quit();
}
+void TestHelper::watcherLastStateNumberChanged(int stateNumber)
+{
+ Q_UNUSED(stateNumber);
+ mLastStateChangedReceived++;
+ if (!mRequestsPending && mNotificationsReceived >= mNotificationsExpected
+ && mLastStateChangedReceived >= mLastStateChangedExpected)
+ mEventLoop.quit();
+}
+
void TestHelper::watcherError(QtJsonDb::QJsonDbWatcher::ErrorCode code, QString msg)
{
qWarning() << "Watcher error:" << code << msg;
diff --git a/tests/shared/testhelper.h b/tests/shared/testhelper.h
index c6e6610f..5d082b5e 100644
--- a/tests/shared/testhelper.h
+++ b/tests/shared/testhelper.h
@@ -50,6 +50,7 @@
#include <QJsonDocument>
#include <QJsonValue>
#include <QObject>
+#include <QProcess>
#include <QStringList>
QT_BEGIN_HEADER
@@ -79,7 +80,8 @@ public:
void waitForResponse(QList<QtJsonDb::QJsonDbRequest*> requests);
void waitForResponseAndNotifications(QtJsonDb::QJsonDbRequest *request,
QtJsonDb::QJsonDbWatcher *watcher,
- int notificationsExpected);
+ int notificationsExpected,
+ int lastStateChangedExpected = 0);
void waitForStatus(QtJsonDb::QJsonDbWatcher *watcher,
QtJsonDb::QJsonDbWatcher::Status status);
@@ -89,10 +91,14 @@ protected:
QEventLoop mEventLoop;
int mNotificationsReceived;
int mNotificationsExpected;
+ int mLastStateChangedExpected;
+ int mLastStateChangedReceived;
protected Q_SLOTS:
void connectionError(QtJsonDb::QJsonDbConnection::ErrorCode code, QString msg);
+ void processFinished(int,QProcess::ExitStatus);
+
void requestFinished();
void requestError(QtJsonDb::QJsonDbRequest::ErrorCode code, QString msg);
void requestStatusChanged(QtJsonDb::QJsonDbRequest::Status status);
@@ -100,6 +106,7 @@ protected Q_SLOTS:
void watcherNotificationsAvailable(int count);
void watcherStatusChanged(QtJsonDb::QJsonDbWatcher::Status status);
void watcherError(QtJsonDb::QJsonDbWatcher::ErrorCode code, QString msg);
+ void watcherLastStateNumberChanged(int stateNumber);
void timeout();
private:
diff --git a/tools/jsondb-client/client.cpp b/tools/jsondb-client/client.cpp
index aba2bcbb..14e9b295 100644
--- a/tools/jsondb-client/client.cpp
+++ b/tools/jsondb-client/client.cpp
@@ -268,8 +268,6 @@ void Client::onNotificationsAvailable(int)
actionString = QStringLiteral("update"); break;
case QtJsonDb::QJsonDbWatcher::Removed:
actionString = QStringLiteral("remove"); break;
- case QtJsonDb::QJsonDbWatcher::StateChanged:
- actionString = QStringLiteral("stateChange"); break;
case QtJsonDb::QJsonDbWatcher::All: break;
}