summaryrefslogtreecommitdiffstats
path: root/src/partition/jsondbreducedefinition.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/partition/jsondbreducedefinition.cpp')
-rw-r--r--src/partition/jsondbreducedefinition.cpp359
1 files changed, 359 insertions, 0 deletions
diff --git a/src/partition/jsondbreducedefinition.cpp b/src/partition/jsondbreducedefinition.cpp
new file mode 100644
index 0000000..ded9357
--- /dev/null
+++ b/src/partition/jsondbreducedefinition.cpp
@@ -0,0 +1,359 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the QtAddOn.JsonDb module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** 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$
+**
+****************************************************************************/
+
+#include <QDebug>
+#include <QElapsedTimer>
+#include <QJSValue>
+#include <QJSValueIterator>
+#include <QStringList>
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#include "jsondbpartition.h"
+#include "jsondbstrings.h"
+#include "jsondberrors.h"
+
+#include "jsondbproxy.h"
+#include "jsondbsettings.h"
+#include "jsondbobjecttable.h"
+#include "jsondbreducedefinition.h"
+#include "jsondbscriptengine.h"
+#include "jsondbview.h"
+
+QT_BEGIN_NAMESPACE_JSONDB_PARTITION
+
+JsonDbReduceDefinition::JsonDbReduceDefinition(const JsonDbOwner *owner, JsonDbPartition *partition,
+ QJsonObject definition, QObject *parent) :
+ QObject(parent)
+ , mOwner(owner)
+ , mPartition(partition)
+ , mDefinition(definition)
+ , mScriptEngine(0)
+ , mUuid(mDefinition.value(JsonDbString::kUuidStr).toString())
+ , mTargetType(mDefinition.value("targetType").toString())
+ , mTargetTable(mPartition->findObjectTable(mTargetType))
+ , mSourceType(mDefinition.value("sourceType").toString())
+{
+ 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()
+{
+ initScriptEngine();
+ initIndexes();
+
+ GetObjectsResult getObjectResponse = mPartition->getObjects(JsonDbString::kTypeStr, mSourceType);
+ if (!getObjectResponse.error.isNull()) {
+ if (jsondbSettings->verbose())
+ qDebug() << "createReduceDefinition" << mTargetType << getObjectResponse.error.toString();
+ setError(getObjectResponse.error.toString());
+ }
+ JsonDbObjectList objects = getObjectResponse.data;
+ for (int i = 0; i < objects.size(); i++)
+ updateObject(QJsonObject(), objects.at(i));
+}
+
+void JsonDbReduceDefinition::definitionRemoved(JsonDbPartition *partition, JsonDbObjectTable *table, const QString targetType, const QString &definitionUuid)
+{
+ if (jsondbSettings->verbose())
+ qDebug() << "Removing Reduce view objects" << targetType;
+ // remove the output objects
+ GetObjectsResult getObjectResponse = table->getObjects(QLatin1String("_reduceUuid"), definitionUuid, targetType);
+ JsonDbObjectList objects = getObjectResponse.data;
+ for (int i = 0; i < objects.size(); i++) {
+ JsonDbObject o = objects[i];
+ o.markDeleted();
+ partition->updateObject(partition->defaultOwner(), o, JsonDbPartition::ViewObject);
+ }
+}
+
+void JsonDbReduceDefinition::initScriptEngine()
+{
+ if (mScriptEngine)
+ return;
+
+ mScriptEngine = JsonDbScriptEngine::scriptEngine();
+ 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());
+}
+
+void JsonDbReduceDefinition::releaseScriptEngine()
+{
+ mFunctions.clear();
+ 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, JsonDbUpdateList *changeList)
+{
+ initScriptEngine();
+
+ QJsonValue beforeKeyValue = sourceKeyValue(before);
+ QJsonValue afterKeyValue = sourceKeyValue(after);
+
+ if (!after.isEmpty() && !before.isEmpty() && (beforeKeyValue != afterKeyValue)) {
+ // do a subtract only on the before key
+ if (!beforeKeyValue.isUndefined())
+ updateObject(before, QJsonObject(), changeList);
+
+ // and then continue here with the add with the after key
+ before = QJsonObject();
+ }
+
+ const QJsonValue keyValue(after.isDeleted() ? beforeKeyValue : afterKeyValue);
+ if (keyValue.isUndefined())
+ return;
+
+ GetObjectsResult getObjectResponse = mTargetTable->getObjects(mTargetKeyName, keyValue, mTargetType);
+ if (!getObjectResponse.error.isNull())
+ setError(getObjectResponse.error.toString());
+
+ JsonDbObject previousObject;
+ QJsonValue previousValue(QJsonValue::Undefined);
+
+ JsonDbObjectList previousResults = getObjectResponse.data;
+ for (int k = 0; k < previousResults.size(); ++k) {
+ JsonDbObject previous = previousResults.at(k);
+ if (previous.value("_reduceUuid").toString() == mUuid) {
+ previousObject = previous;
+ previousValue = previousObject;
+ break;
+ }
+ }
+
+ QJsonValue value = previousValue;
+ if (!before.isEmpty())
+ value = addObject(JsonDbReduceDefinition::Subtract, keyValue, value, before);
+ if (!after.isDeleted())
+ value = addObject(JsonDbReduceDefinition::Add, keyValue, value, after);
+
+ JsonDbObjectList objectsToUpdate;
+ // if we had a previous object to reduce
+ if (previousObject.contains(JsonDbString::kUuidStr)) {
+ // and now the value is undefined
+ if (value.isUndefined()) {
+ // then remove it
+ previousObject.markDeleted();
+ objectsToUpdate.append(previousObject);
+ } else {
+ //otherwise update it
+ JsonDbObject reduced(value.toObject());
+ reduced.insert(JsonDbString::kTypeStr, mTargetType);
+ reduced.insert(JsonDbString::kUuidStr,
+ previousObject.value(JsonDbString::kUuidStr));
+ reduced.insert(JsonDbString::kVersionStr,
+ previousObject.value(JsonDbString::kVersionStr));
+ reduced.insert(mTargetKeyName, keyValue);
+ reduced.insert("_reduceUuid", mUuid);
+ objectsToUpdate.append(reduced);
+ }
+ } else if (!value.isUndefined()) {
+ // otherwise create the new object
+ JsonDbObject reduced(value.toObject());
+ reduced.insert(JsonDbString::kTypeStr, mTargetType);
+ reduced.insert(mTargetKeyName, keyValue);
+ reduced.insert("_reduceUuid", mUuid);
+
+ objectsToUpdate.append(reduced);
+ }
+
+ JsonDbWriteResult res = mPartition->updateObjects(mOwner, objectsToUpdate, JsonDbPartition::ViewObject, changeList);
+ if (res.code != JsonDbError::NoError)
+ setError(QString("Error executing add function: %1").arg(res.message));
+}
+
+QJsonValue JsonDbReduceDefinition::addObject(JsonDbReduceDefinition::FunctionNumber functionNumber,
+ const QJsonValue &keyValue, QJsonValue previousValue, JsonDbObject object)
+{
+ initScriptEngine();
+ QJSValue svKeyValue = JsonDbScriptEngine::toJSValue(keyValue, mScriptEngine);
+ if (!mTargetValueName.isEmpty())
+ previousValue = previousValue.toObject().value(mTargetValueName);
+ QJSValue svPreviousValue = JsonDbScriptEngine::toJSValue(previousValue, mScriptEngine);
+ QJSValue svObject = JsonDbScriptEngine::toJSValue(object, mScriptEngine);
+
+ QJSValueList reduceArgs;
+ reduceArgs << svKeyValue << svPreviousValue << svObject;
+ QJSValue reduced = mFunctions[functionNumber].call(reduceArgs);
+
+ if (!reduced.isUndefined() && !reduced.isError()) {
+ QJsonValue jsonReduced = JsonDbScriptEngine::fromJSValue(reduced);
+ QJsonObject jsonReducedObject;
+ if (!mTargetValueName.isEmpty())
+ jsonReducedObject.insert(mTargetValueName, jsonReduced);
+ else
+ jsonReducedObject = jsonReduced.toObject();
+ return jsonReducedObject;
+ } else {
+
+ if (reduced.isError())
+ setError("Error executing add function: " + reduced.toString());
+
+ return QJsonValue(QJsonValue::Undefined);
+ }
+}
+
+bool JsonDbReduceDefinition::isActive() const
+{
+ return !mDefinition.contains(JsonDbString::kActiveStr) || mDefinition.value(JsonDbString::kActiveStr).toBool();
+}
+
+void JsonDbReduceDefinition::setError(const QString &errorMsg)
+{
+ mDefinition.insert(JsonDbString::kActiveStr, false);
+ mDefinition.insert(JsonDbString::kErrorStr, errorMsg);
+ if (mPartition)
+ mPartition->updateObject(mOwner, mDefinition, JsonDbPartition::ForcedWrite);
+}
+
+bool JsonDbReduceDefinition::validateDefinition(const JsonDbObject &reduce, JsonDbPartition *partition, QString &message)
+{
+ message.clear();
+ QString targetType = reduce.value("targetType").toString();
+ QString sourceType = reduce.value("sourceType").toString();
+ QString uuid = reduce.value(JsonDbString::kUuidStr).toString();
+ JsonDbView *view = partition->findView(targetType);
+
+ if (targetType.isEmpty())
+ message = QLatin1Literal("targetType property for Reduce not specified");
+ else if (!view)
+ message = QLatin1Literal("targetType must be of a type that extends View");
+ else if (sourceType.isEmpty())
+ message = QLatin1Literal("sourceType property for Reduce not specified");
+ else if (view->mReduceDefinitionsBySource.contains(sourceType)
+ && 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() && 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 {
+ QJSEngine *scriptEngine = JsonDbScriptEngine::scriptEngine();
+ 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 JsonDbObject &object)
+{
+ if (object.isEmpty() || object.isDeleted()) {
+ return QJsonValue(QJsonValue::Undefined);
+ } else if (mFunctions[JsonDbReduceDefinition::SourceKeyValue].isCallable()) {
+ QJSValueList args;
+ args << JsonDbScriptEngine::toJSValue(object, mScriptEngine);
+ QJsonValue keyValue = JsonDbScriptEngine::fromJSValue(mFunctions[JsonDbReduceDefinition::SourceKeyValue].call(args));
+ return keyValue;
+ } else
+ return mSourceKeyName.contains(".") ? JsonDbObject(object).propertyLookup(mSourceKeyNameList) : object.value(mSourceKeyName);
+
+}
+
+#include "moc_jsondbreducedefinition.cpp"
+
+QT_END_NAMESPACE_JSONDB_PARTITION