diff options
author | Jamey Hicks <jamey.hicks@nokia.com> | 2012-03-22 09:57:37 -0400 |
---|---|---|
committer | Qt by Nokia <qt-info@nokia.com> | 2012-03-26 19:19:22 +0200 |
commit | 148586f1ae531c0ecda68ee5d1bf80f123738c14 (patch) | |
tree | 0890ee1565ccff62ec20eb5ae3f04c257b7cd48b | |
parent | a3f84f262af945ac44e95902d8256662575bbef2 (diff) |
Updated Reduce
* Added sourceKeyFunction for determining source key as alternative to sourceKeyName
* If targetValueName is null, use whole object returned from
add/subtract as result of reduce. This will be the only behavior
once all Reduces are converted.
* Combined some redundant code in JsonDbReduceDefinition.
* Added basic Reduce documentation.
Change-Id: I258d9e24bb5fa620634b1168832ee4bbc1d58aca
Reviewed-by: Kevin Simons <kevin.simons@nokia.com>
-rw-r--r-- | doc/src/index.qdoc | 2 | ||||
-rw-r--r-- | doc/src/reduce-views.qdoc | 136 | ||||
-rw-r--r-- | src/daemon/jsondbreducedefinition.cpp | 151 | ||||
-rw-r--r-- | src/daemon/jsondbreducedefinition.h | 18 | ||||
-rw-r--r-- | tests/auto/daemon/json/reduce.json | 4 | ||||
-rw-r--r-- | tests/auto/daemon/testjsondb.cpp | 111 |
6 files changed, 345 insertions, 77 deletions
diff --git a/doc/src/index.qdoc b/doc/src/index.qdoc index 4026e8c..6e8842a 100644 --- a/doc/src/index.qdoc +++ b/doc/src/index.qdoc @@ -110,6 +110,7 @@ sets. \list \li \l {Map and Join Views} +\li \l {Reduce Views} \endlist \section2 Notifications @@ -134,6 +135,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/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/daemon/jsondbreducedefinition.cpp b/src/daemon/jsondbreducedefinition.cpp index f591fd5..1021413 100644 --- a/src/daemon/jsondbreducedefinition.cpp +++ b/src/daemon/jsondbreducedefinition.cpp @@ -73,11 +73,20 @@ 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() @@ -115,7 +124,7 @@ void JsonDbReduceDefinition::initScriptEngine() mScriptEngine = new QJSEngine(this); QString message; - bool status = compileFunctions(mScriptEngine, mDefinition, mAddFunction, mSubtractFunction, message); + bool status = compileFunctions(mScriptEngine, mDefinition, mFunctions, message); if (!status) setError(message); @@ -128,14 +137,19 @@ void JsonDbReduceDefinition::initScriptEngine() 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); } @@ -143,14 +157,9 @@ void JsonDbReduceDefinition::initIndexes() 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 @@ -184,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 @@ -222,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()) @@ -246,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(); @@ -302,45 +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; - QJSValue addFunction, subtractFunction; + QVector<QJSValue> functions; // check for script errors - compileFunctions(scriptEngine, reduce, addFunction, subtractFunction, message); + compileFunctions(scriptEngine, reduce, functions, message); scriptEngine->collectGarbage(); } return message.isEmpty(); } -bool JsonDbReduceDefinition::compileFunctions(QJSEngine *scriptEngine, QJsonObject definition, QJSValue &addFunction, QJSValue &subtractFunction, QString &message) +bool JsonDbReduceDefinition::compileFunctions(QJSEngine *scriptEngine, QJsonObject definition, + QVector<QJSValue> &functions, QString &message) { - Q_ASSERT(!definition.value("add").toString().isEmpty()); - Q_ASSERT(!definition.value("subtract").toString().isEmpty()); - QString script = definition.value("add").toString(); - QJSValue result = scriptEngine->evaluate(QString("var %1 = (%2); %1;").arg("add").arg(script)); - - if (result.isError() || !result.isCallable()) { - message = QString("Unable to parse add function: %1").arg(result.toString()); - return false; + 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; } - addFunction = result; + return status; +} - script = definition.value("subtract").toString(); - result = scriptEngine->evaluate(QString("var %1 = (%2); %1;").arg("subtract").arg(script)); +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); - if (result.isError() || !result.isCallable()) { - message = QString("Unable to parse subtract function: %1").arg(result.toString()); - return false; - } - subtractFunction = result; - return true; } QT_END_NAMESPACE_JSONDB diff --git a/src/daemon/jsondbreducedefinition.h b/src/daemon/jsondbreducedefinition.h index 6530a82..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); @@ -89,21 +87,27 @@ public: 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); - static bool compileFunctions(QJSEngine *scriptEngine, QJsonObject definition, QJSValue &addFunction, QJSValue &subFunction, 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/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 3532faa..ac37b35 100644 --- a/tests/auto/daemon/testjsondb.cpp +++ b/tests/auto/daemon/testjsondb.cpp @@ -175,6 +175,8 @@ private slots: void mapSchemaViolation(); void mapArrayConversion(); void reduce(); + void reduceFlattened(); + void reduceSourceKeyFunction(); void reduceRemoval(); void reduceUpdate(); void reduceDuplicate(); @@ -1556,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)); } @@ -2182,6 +2195,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()); |