diff options
author | Denis Dzyubenko <denis.dzyubenko@nokia.com> | 2012-04-11 15:59:00 +0200 |
---|---|---|
committer | Denis Dzyubenko <denis.dzyubenko@nokia.com> | 2012-04-11 16:54:46 +0200 |
commit | 6b7df60a752422b1f6ff07bb2682c21e18a43985 (patch) | |
tree | 3e56bd3f4ee34a228777d00ff328ef1c53cb1df1 /src/partition | |
parent | 20549eedb5b5e852c0e384cd76bfbff14168dbda (diff) | |
parent | 7ef36e3c5a88560eb4e3a81c2c9f14059739108b (diff) |
Merge remote-tracking branch 'gerrit/master' into hbtreehbtree
Conflicts:
src/daemon/daemon.pri
src/daemon/jsondbview.cpp
src/partition/jsondbindex.cpp
src/partition/jsondbindex.h
src/partition/jsondbindexquery.h
src/partition/jsondbmanagedbtree.cpp
src/partition/jsondbmanagedbtree.h
src/partition/jsondbmanagedbtreetxn.cpp
src/partition/jsondbmanagedbtreetxn.h
src/partition/jsondbobjecttable.cpp
src/partition/jsondbobjecttable.h
src/partition/jsondbpartition.cpp
src/partition/jsondbpartition.h
tests/auto/auto.pro
tests/auto/partition/testpartition.cpp
tests/benchmarks/benchmarks.pro
Change-Id: I963adefd6d32fca9b3537981306b67538c759034
Diffstat (limited to 'src/partition')
53 files changed, 12782 insertions, 0 deletions
diff --git a/src/partition/jsondb.qrc b/src/partition/jsondb.qrc new file mode 100644 index 00000000..2a12e110 --- /dev/null +++ b/src/partition/jsondb.qrc @@ -0,0 +1,9 @@ +<RCC> + <qresource prefix="/"> + <file>schema/notification.json</file> + <file>schema/View.json</file> + <file>schema/Capability.json</file> + <file>schema/RootCapability.json</file> + <file>schema/Index.json</file> + </qresource> +</RCC> diff --git a/src/partition/jsondbbtree.cpp b/src/partition/jsondbbtree.cpp new file mode 100644 index 00000000..14c7bb30 --- /dev/null +++ b/src/partition/jsondbbtree.cpp @@ -0,0 +1,164 @@ +/**************************************************************************** +** +** 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 <QFile> +#include <errno.h> + +#include "jsondbbtree.h" +#include "jsondbsettings.h" + +QT_BEGIN_NAMESPACE_JSONDB_PARTITION + +JsonDbBtree::JsonDbBtree() + : mBtree(new Btree()) +{ +#ifndef JSONDB_USE_HBTREE + mBtree->setAutoCompactRate(jsondbSettings->compactRate()); +#endif +} + +JsonDbBtree::~JsonDbBtree() +{ + close(); + delete mBtree; +} + +bool JsonDbBtree::open(const QString &filename, OpenFlags flags) +{ + mBtree->setFileName(filename); +#ifdef JSONDB_USE_HBTREE + HBtree::OpenMode mode = HBtree::ReadWrite; + if (flags & ReadOnly) + mode = HBtree::ReadOnly; + mBtree->setOpenMode(mode); + return mBtree->open(); +#else + QBtree::DbFlags dbFlags = QBtree::UseSyncMarker | QBtree::NoSync; + if (flags & ReadOnly) + dbFlags |= QBtree::ReadOnly; + return mBtree->open(flags); +#endif +} + +void JsonDbBtree::close() +{ + Q_ASSERT(mBtree); + mBtree->close(); +} + +bool JsonDbBtree::putOne(const QByteArray &key, const QByteArray &value) +{ + bool inTransaction = mBtree->isWriting(); + Transaction *txn = inTransaction ? mBtree->writeTransaction() : mBtree->beginWrite(); + bool ok = txn->put(key, value); + if (!inTransaction) { + qWarning() << "JsonDbBtree::putOne" << "auto commiting tag 0"; + ok &= txn->commit(0); + } + return ok; +} + +bool JsonDbBtree::getOne(const QByteArray &key, QByteArray *value) +{ + bool inTransaction = mBtree->isWriting(); + Transaction *txn = inTransaction ? mBtree->writeTransaction() : mBtree->beginWrite(); + bool ok = txn->get(key, value); + if (!inTransaction) + txn->abort(); + return ok; +} + +bool JsonDbBtree::removeOne(const QByteArray &key) +{ + bool inTransaction = mBtree->isWriting(); + Transaction *txn = inTransaction ? mBtree->writeTransaction() : mBtree->beginWrite(); + bool ok = txn->remove(key); + if (!inTransaction){ + qWarning() << "JsonDbBtree::removeOne" << "auto commiting tag 0"; + ok &= txn->commit(0); + } + return ok; +} + +bool JsonDbBtree::clearData() +{ + Q_ASSERT(isWriting() == false); + close(); + QFile::remove(mBtree->fileName()); + return mBtree->open(); +} + +bool JsonDbBtree::compact() +{ + Q_ASSERT(mBtree); +#ifdef JSONDB_USE_HBTREE + return true; +#else + return mBtree->compact(); +#endif +} + +bool JsonDbBtree::rollback() +{ + Q_ASSERT(mBtree && !isWriting()); + return mBtree->rollback(); +} + +void JsonDbBtree::setAutoCompactRate(int rate) const +{ + Q_ASSERT(mBtree); +#ifdef JSONDB_USE_HBTREE + Q_UNUSED(rate); +#else + mBtree->setAutoCompactRate(rate); +#endif +} + +JsonDbBtree::Stat JsonDbBtree::stats() const +{ + if (mBtree) + return mBtree->stats(); + else + return Stat(); +} + +QT_END_NAMESPACE_JSONDB_PARTITION diff --git a/src/partition/jsondbbtree.h b/src/partition/jsondbbtree.h new file mode 100644 index 00000000..18387376 --- /dev/null +++ b/src/partition/jsondbbtree.h @@ -0,0 +1,142 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef JSONDB_BTREE_H +#define JSONDB_BTREE_H + +#define JSONDB_USE_HBTREE + +#include "jsondbpartitionglobal.h" + +#ifndef JSONDB_USE_HBTREE +#include "qbtree.h" +#include "qbtreecursor.h" +#include "qbtreetxn.h" +#else +#include "hbtree.h" +#include "hbtreecursor.h" +#include "hbtreetransaction.h" +#endif + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE_JSONDB_PARTITION + +class JsonDbBtree +{ +public: + + enum OpenFlag { + Default, + ReadOnly + }; + Q_DECLARE_FLAGS(OpenFlags, OpenFlag) + +#ifdef JSONDB_USE_HBTREE + typedef HBtree Btree; +#else + typedef QBtree Btree; +#endif + + typedef Btree::CursorType Cursor; + typedef Btree::TransactionType Transaction; + typedef Btree::StatType Stat; + + typedef int (*CompareFunction)(const QByteArray &, const QByteArray &); + + JsonDbBtree(); + ~JsonDbBtree(); + + bool open(const QString &filename, OpenFlags flags = Default); + void close(); + + Transaction *beginRead() + { Q_ASSERT(mBtree); return mBtree->beginRead(); } + Transaction *beginWrite() + { Q_ASSERT(mBtree); return mBtree->beginWrite(); } + + bool isWriting() const + { Q_ASSERT(mBtree); return mBtree->isWriting(); } + + Transaction *writeTransaction() + { Q_ASSERT(mBtree); return mBtree->writeTransaction(); } + + QString errorMessage() const + { Q_ASSERT(mBtree); return mBtree->errorMessage(); } + + QString fileName() const + { Q_ASSERT(mBtree); return mBtree->fileName(); } + quint64 count() const + { Q_ASSERT(mBtree); return mBtree->count(); } + quint32 tag() const + { Q_ASSERT(mBtree); return mBtree->tag(); } + void setCompareFunction(CompareFunction cmp) + { Q_ASSERT(mBtree); mBtree->setCompareFunction(cmp); } + void setCacheSize(int size) + { Q_ASSERT(mBtree); mBtree->setCacheSize(size); } + Btree *btree() const + { return mBtree; } + Stat stats() const; + bool sync() + { Q_ASSERT(mBtree); return mBtree->sync(); } + + bool putOne(const QByteArray &key, const QByteArray &value); + bool getOne(const QByteArray &key, QByteArray *value); + bool removeOne(const QByteArray &key); + + bool clearData(); + + bool compact(); + bool rollback(); + void setAutoCompactRate(int rate) const; + +private: + Btree *mBtree; + JsonDbBtree(const JsonDbBtree&); +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(JsonDbBtree::OpenFlags) + +QT_END_NAMESPACE_JSONDB_PARTITION + +QT_END_HEADER + +#endif // JSONDB_BTREE_H diff --git a/src/partition/jsondbcollator.cpp b/src/partition/jsondbcollator.cpp new file mode 100644 index 00000000..869bf3f8 --- /dev/null +++ b/src/partition/jsondbcollator.cpp @@ -0,0 +1,341 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef NO_COLLATION_SUPPORT + +#include "jsondbcollator.h" +#include "jsondbcollator_p.h" + +#include <unicode/utypes.h> +#include <unicode/ucol.h> +#include <unicode/ustring.h> + +#include "qdebug.h" + +QT_BEGIN_NAMESPACE_JSONDB_PARTITION + +JsonDbCollatorPrivate::~JsonDbCollatorPrivate() +{ + if (collator) + ucol_close(collator); +} + +static const int collationStringsCount = 13; +static const char * const collationStrings[collationStringsCount] = { + "default", + "big5han", + "dict", + "direct", + "gb2312", + "phonebk", + "pinyin", + "phonetic", + "reformed", + "standard", + "stroke", + "trad", + "unihan" +}; + +JsonDbCollator::JsonDbCollator(const QLocale &locale, JsonDbCollator::Collation collation) + : d(new JsonDbCollatorPrivate) +{ + d->locale = locale; + if ((int)collation >= 0 && (int)collation < collationStringsCount) + d->collation = collation; + + init(); +} + +JsonDbCollator::JsonDbCollator(const JsonDbCollator &other) + : d(other.d) +{ + d->ref.ref(); +} + +JsonDbCollator::~JsonDbCollator() +{ + if (!d->ref.deref()) + delete d; +} + +JsonDbCollator &JsonDbCollator::operator=(const JsonDbCollator &other) +{ + if (this != &other) { + if (!d->ref.deref()) + delete d; + *d = *other.d; + d->ref.ref(); + } + return *this; +} + +void JsonDbCollator::setLocale(const QLocale &locale) +{ + if (d->ref.load() != 1) + detach(); + if (d->collator) + ucol_close(d->collator); + d->locale = locale; + + init(); +} + +QLocale JsonDbCollator::locale() const +{ + return d->locale; +} + +void JsonDbCollator::setCollation(JsonDbCollator::Collation collation) +{ + if ((int)collation < 0 || (int)collation >= collationStringsCount) + return; + + if (d->ref.load() != 1) + detach(); + if (d->collator) + ucol_close(d->collator); + d->collation = collation; + + init(); +} + +JsonDbCollator::Collation JsonDbCollator::collation() const +{ + return d->collation; +} + +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('_')) + QLatin1String("@collation=") + QLatin1String(collationString)).toLatin1(); + d->collator = ucol_open(name.constData(), &status); + if (U_FAILURE(status)) + qWarning("Could not create collator: %d", status); + + // enable normalization by default + ucol_setAttribute(d->collator, UCOL_NORMALIZATION_MODE, UCOL_ON, &status); + + // fetch options from the collator + d->options = 0; + + switch (ucol_getAttribute(d->collator, UCOL_CASE_FIRST, &status)) { + case UCOL_UPPER_FIRST: d->options |= JsonDbCollator::PreferUpperCase; break; + case UCOL_LOWER_FIRST: d->options |= JsonDbCollator::PreferLowerCase; break; + case UCOL_OFF: + default: + break; + } + + switch (ucol_getAttribute(d->collator, UCOL_FRENCH_COLLATION, &status)) { + case UCOL_ON: d->options |= JsonDbCollator::FrenchCollation; break; + case UCOL_OFF: + default: + break; + } + + switch (ucol_getAttribute(d->collator, UCOL_ALTERNATE_HANDLING, &status)) { + case UCOL_SHIFTED: d->options |= JsonDbCollator::IgnorePunctuation; break; + case UCOL_NON_IGNORABLE: + default: + break; + } + + + switch (ucol_getAttribute(d->collator, UCOL_CASE_LEVEL, &status)) { + case UCOL_ON: d->options |= JsonDbCollator::ExtraCaseLevel; break; + case UCOL_OFF: + default: + break; + } + + switch (ucol_getAttribute(d->collator, UCOL_HIRAGANA_QUATERNARY_MODE, &status)) { + case UCOL_ON: d->options |= JsonDbCollator::HiraganaQuaternaryMode; break; + case UCOL_OFF: + default: + break; + } + + switch (ucol_getAttribute(d->collator, UCOL_NUMERIC_COLLATION, &status)) { + case UCOL_ON: d->options |= JsonDbCollator::NumericMode; break; + case UCOL_OFF: + default: + break; + } +} + +void JsonDbCollator::detach() +{ + if (d->ref.load() != 1) { + JsonDbCollatorPrivate *x = new JsonDbCollatorPrivate; + x->ref.store(1); + x->strength = d->strength; + x->options = d->options; + x->modified = true; + x->collator = 0; + if (!d->ref.deref()) + delete d; + d = x; + } +} + +void JsonDbCollator::setStrength(JsonDbCollator::Strength strength) +{ + if (d->ref.load() != 1) + detach(); + + switch (strength) { + case JsonDbCollator::PrimaryStrength: ucol_setStrength(d->collator, UCOL_PRIMARY); break; + case JsonDbCollator::SecondaryStrength: ucol_setStrength(d->collator, UCOL_SECONDARY); break; + case JsonDbCollator::TertiaryStrength: ucol_setStrength(d->collator, UCOL_TERTIARY); break; + case JsonDbCollator::QuaternaryStrength: ucol_setStrength(d->collator, UCOL_QUATERNARY); break; + case JsonDbCollator::IdenticalStrength: ucol_setStrength(d->collator, UCOL_IDENTICAL); break; + default: + qWarning("Invalid strength mode %d", strength); + } +} + +JsonDbCollator::Strength JsonDbCollator::strength() const +{ + switch (ucol_getStrength(d->collator)) { + case UCOL_PRIMARY: return JsonDbCollator::PrimaryStrength; + case UCOL_SECONDARY: return JsonDbCollator::SecondaryStrength; + case UCOL_QUATERNARY:return JsonDbCollator::QuaternaryStrength; + case UCOL_IDENTICAL: return JsonDbCollator::IdenticalStrength; + case UCOL_TERTIARY: + default: + return JsonDbCollator::TertiaryStrength; + } + return JsonDbCollator::TertiaryStrength; +} + +void JsonDbCollator::setOptions(JsonDbCollator::Options options) +{ + if (d->ref.load() != 1) + detach(); + + d->options = options; + + UErrorCode status = U_ZERO_ERROR; + + if (options & JsonDbCollator::PreferUpperCase) + ucol_setAttribute(d->collator, UCOL_CASE_FIRST, UCOL_UPPER_FIRST, &status); + else if (options & JsonDbCollator::PreferLowerCase) + ucol_setAttribute(d->collator, UCOL_CASE_FIRST, UCOL_LOWER_FIRST, &status); + else + ucol_setAttribute(d->collator, UCOL_CASE_FIRST, UCOL_OFF, &status); + if (U_FAILURE(status)) + qWarning("ucol_setAttribute: Case First failed: %d", status); + + ucol_setAttribute(d->collator, UCOL_FRENCH_COLLATION, + options & JsonDbCollator::FrenchCollation ? UCOL_ON : UCOL_OFF, &status); + if (U_FAILURE(status)) + qWarning("ucol_setAttribute: French collation failed: %d", status); + + ucol_setAttribute(d->collator, UCOL_NORMALIZATION_MODE, + options & JsonDbCollator::DisableNormalization ? UCOL_OFF : UCOL_ON, &status); + if (U_FAILURE(status)) + qWarning("ucol_setAttribute: Normalization mode failed: %d", status); + + ucol_setAttribute(d->collator, UCOL_ALTERNATE_HANDLING, + (options & JsonDbCollator::IgnorePunctuation) ? UCOL_SHIFTED : UCOL_NON_IGNORABLE, &status); + if (U_FAILURE(status)) + qWarning("ucol_setAttribute: Alternate handling failed: %d", status); + + ucol_setAttribute(d->collator, UCOL_CASE_LEVEL, + options & JsonDbCollator::ExtraCaseLevel ? UCOL_ON : UCOL_OFF, &status); + if (U_FAILURE(status)) + qWarning("ucol_setAttribute: Case level failed: %d", status); + + ucol_setAttribute(d->collator, UCOL_HIRAGANA_QUATERNARY_MODE, + options & JsonDbCollator::HiraganaQuaternaryMode ? UCOL_ON : UCOL_OFF, &status); + if (U_FAILURE(status)) + qWarning("ucol_setAttribute: hiragana mode failed: %d", status); + + ucol_setAttribute(d->collator, UCOL_NUMERIC_COLLATION, + options & JsonDbCollator::NumericMode ? UCOL_ON : UCOL_OFF, &status); + if (U_FAILURE(status)) + qWarning("ucol_setAttribute: numeric collation failed: %d", status); +} + +JsonDbCollator::Options JsonDbCollator::options() const +{ + return d->options; +} + +int JsonDbCollator::compare(const QString &s1, const QString &s2) const +{ + return d->compare((ushort *)s1.constData(), s1.size(), (ushort *)s2.constData(), s2.size()); +} + +int JsonDbCollator::compare(const QStringRef &s1, const QStringRef &s2) const +{ + return d->compare((ushort *)s1.constData(), s1.size(), (ushort *)s2.constData(), s2.size()); +} + +int JsonDbCollatorPrivate::compare(ushort *s1, int len1, ushort *s2, int len2) +{ + const UCollationResult result = + ucol_strcoll(collator, (const UChar *)s1, len1, (const UChar *)s2, len2); + return result; +} + +QByteArray JsonDbCollator::sortKey(const QString &string) const +{ + QByteArray result(16 + string.size() + (string.size() >> 2), Qt::Uninitialized); + int size = ucol_getSortKey(d->collator, (const UChar *)string.constData(), + string.size(), (uint8_t *)result.data(), result.size()); + if (size > result.size()) { +#ifdef _DEBUG + qDebug() << "### sortKey realloc from" << result.size() << "to" << size << "for string" << string.size(); +#endif + result.resize(size); + size = ucol_getSortKey(d->collator, (const UChar *)string.constData(), + string.size(), (uint8_t *)result.data(), result.size()); + } + result.truncate(size); + return result; +} +QT_END_NAMESPACE_JSONDB_PARTITION + +#endif // NO_COLLATION_SUPPORT diff --git a/src/partition/jsondbcollator.h b/src/partition/jsondbcollator.h new file mode 100644 index 00000000..d23ea1d1 --- /dev/null +++ b/src/partition/jsondbcollator.h @@ -0,0 +1,173 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef JSONDBCOLLATOR_H +#define JSONDBCOLLATOR_H + +#ifndef NO_COLLATION_SUPPORT + +#include <QString> +#include <QLocale> + +#include "jsondbpartitionglobal.h" + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE_JSONDB_PARTITION + +class JsonDbCollatorPrivate; + +class Q_JSONDB_PARTITION_EXPORT JsonDbCollator +{ +public: + enum Strength { + PrimaryStrength = 1, + BaseLetterStrength = PrimaryStrength, + + SecondaryStrength = 2, + AccentsStrength = SecondaryStrength, + + TertiaryStrength = 3, + CaseStrength = TertiaryStrength, + + QuaternaryStrength = 4, + PunctuationStrength = QuaternaryStrength, + + IdenticalStrength = 5, + CodepointStrength = IdenticalStrength + }; + + enum Option { + PreferUpperCase = 0x01, + PreferLowerCase = 0x02, + FrenchCollation = 0x04, + DisableNormalization = 0x08, + IgnorePunctuation = 0x10, + ExtraCaseLevel = 0x20, + HiraganaQuaternaryMode = 0x40, + NumericMode = 0x80 + }; + Q_DECLARE_FLAGS(Options, Option) + + enum Collation { + Default, + Big5Han, + Dictionary, + Direct, + GB2312Han, + PhoneBook, + Pinyin, + Phonetic, + Reformed, + Standard, + Stroke, + Traditional, + UniHan + }; + + JsonDbCollator(const QLocale &locale = QLocale(), JsonDbCollator::Collation collation = JsonDbCollator::Default); + JsonDbCollator(const JsonDbCollator &); + ~JsonDbCollator(); + JsonDbCollator &operator=(const JsonDbCollator &); + + void setLocale(const QLocale &locale); + QLocale locale() const; + + void setCollation(Collation collation); + Collation collation() const; + + void setStrength(Strength); + Strength strength() const; + + void setOptions(Options); + Options options() const; + + enum CasePreference { + IgnoreCase = 0x0, + UpperCase = 0x1, + LowerCase = 0x2 + }; + + bool isCaseSensitive() const + { return options() & (PreferUpperCase | PreferLowerCase); } + CasePreference casePreference() const + { + int value = options() & 0x3; + if (value == 3) + return UpperCase; + return CasePreference(value); + } + void setCasePreference(CasePreference c) + { + Options o = options() & ~(PreferUpperCase | PreferLowerCase); + if (c == UpperCase) + o |= PreferUpperCase; + else if (c == LowerCase) + o |= PreferLowerCase; + setOptions(o); + } + + void setNumericMode(bool on) + { setOptions(on ? options() | NumericMode : options() & ~NumericMode); } + bool numericMode() const + { return options() & NumericMode; } + + int compare(const QString &s1, const QString &s2) const; + int compare(const QStringRef &s1, const QStringRef &s2) const; + bool operator()(const QString &s1, const QString &s2) const + { return compare(s1, s2) < 0; } + + QByteArray sortKey(const QString &string) const; + +private: + JsonDbCollatorPrivate *d; + + void detach(); + void init(); +}; + +QT_END_NAMESPACE_JSONDB_PARTITION + +QT_END_HEADER + +#endif // NO_COLLATION_SUPPORT + +#endif // JSONDBCOLLATOR_H diff --git a/src/partition/jsondbcollator_p.h b/src/partition/jsondbcollator_p.h new file mode 100644 index 00000000..1ea44fd7 --- /dev/null +++ b/src/partition/jsondbcollator_p.h @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef JSONDBCOLLATOR_P_H +#define JSONDBCOLLATOR_P_H + +#ifndef NO_COLLATION_SUPPORT + +#include "jsondbcollator.h" + +#include <unicode/utypes.h> +#include <unicode/ucol.h> +#include <unicode/ustring.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE_JSONDB_PARTITION + +class JsonDbCollatorPrivate +{ +public: + QAtomicInt ref; + bool modified; + QLocale locale; + JsonDbCollator::Collation collation; + JsonDbCollator::Strength strength; + JsonDbCollator::Options options; + + UCollator *collator; + + JsonDbCollatorPrivate() + : modified(true), + collation(JsonDbCollator::Default), + strength(JsonDbCollator::TertiaryStrength), + options(0), + collator(0) + { ref.store(1); } + ~JsonDbCollatorPrivate(); + + int compare(ushort *s1, int len1, ushort *s2, int len2); +}; + +QT_END_NAMESPACE_JSONDB_PARTITION + +QT_END_HEADER + +#endif // NO_COLLATION_SUPPORT + +#endif // JSONDBCOLLATOR_P_H diff --git a/src/partition/jsondberrors.cpp b/src/partition/jsondberrors.cpp new file mode 100644 index 00000000..3a66ab49 --- /dev/null +++ b/src/partition/jsondberrors.cpp @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** 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 "jsondbpartitionglobal.h" +#include "jsondberrors.h" + +QT_BEGIN_NAMESPACE_JSONDB_PARTITION + +/*! + \class JsonDbError + \brief The JsonDbError class lists possible error codes. + \sa JsonDbError::ErrorCode + */ + +/*! + \enum JsonDbError::ErrorCode + \omitvalue NoError + \value InvalidMessage + Unable to parse the query message. + \value InvalidRequest + Request object doesn't contain correct elements. + \value MissingObject + Invalid or missing "object" field. + \value DatabaseError + Error directly from the database. + \value MissingUUID + Missing id field. + \value MissingType + Missing _type field. + \value MissingQuery + Missing query field. + \value InvalidLimit + Invalid limit field. + \value InvalidOffset + Invalid offset field. + \value MismatchedNotifyId + Request to delete notify doesn't match existing notification. + \value InvalidActions + List of actions supplied to setNotification is invalid. + \value UpdatingStaleVersion + Updating stale version of object. + \value OperationNotPermitted + Operation prohibited by access control policy. + \value QuotaExceeded + Operation would exceed quota. + \value FailedSchemaValidation + Object to be created/updated was invalid according to the schema. + \value InvalidMap + The Map definition is invalid. + \value InvalidReduce + The Reduce definition is invalid. + \value InvalidSchemaOperation + Attempted to create a schema that already exists or to remove a schema when there are still objects belonging to the schema's type. + \value InvalidPartition + Invalid partition. + \value InvalidIndexOperation + An error when creating an index object + */ + +QT_END_NAMESPACE_JSONDB_PARTITION diff --git a/src/partition/jsondberrors.h b/src/partition/jsondberrors.h new file mode 100644 index 00000000..482d25e3 --- /dev/null +++ b/src/partition/jsondberrors.h @@ -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 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$ +** +****************************************************************************/ + +#ifndef JSONDB_ERRORS_H +#define JSONDB_ERRORS_H + +#include "jsondbpartitionglobal.h" + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE_JSONDB_PARTITION + +class Q_JSONDB_PARTITION_EXPORT JsonDbError { +public: + enum ErrorCode { + // common errors + NoError = 0, + InvalidRequest = 1, + OperationNotPermitted = 2, + InvalidPartition = 3, + DatabaseConnectionError = 4, + + // read / notify errors + MissingQuery = 5, + InvalidMessage= 6, + InvalidLimit = 7, + InvalidOffset = 8, + InvalidStateNumber = 9, + + // write errors + MissingObject = 10, + DatabaseError = 11, + MissingUUID = 12, + MissingType = 13, + UpdatingStaleVersion = 14, + QuotaExceeded = 15, + FailedSchemaValidation = 16, + InvalidMap = 17, + InvalidReduce = 18, + InvalidSchemaOperation = 19, + InvalidIndexOperation = 20, + InvalidType = 21 + }; +}; + +QT_END_NAMESPACE_JSONDB_PARTITION + +QT_END_HEADER + +#endif // JSONDB_ERRORS_H diff --git a/src/partition/jsondbindex.cpp b/src/partition/jsondbindex.cpp new file mode 100644 index 00000000..568fcdf3 --- /dev/null +++ b/src/partition/jsondbindex.cpp @@ -0,0 +1,607 @@ +/**************************************************************************** +** +** 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 <QObject> +#include <QByteArray> +#include <QVariant> +#include <QFile> +#include <QFileInfo> +#include <QDir> +#include <QLocale> + +#include "jsondbstrings.h" +#include "jsondbproxy.h" +#include "jsondbindex.h" +#include "jsondbbtree.h" +#include "jsondbsettings.h" +#include "jsondbobjecttable.h" +#include "jsondbscriptengine.h" +#include "qbtree.h" +#include "qbtreecursor.h" +#include "qbtreetxn.h" + +QT_BEGIN_NAMESPACE_JSONDB_PARTITION + +static const int collationStringsCount = 13; +static const char * const collationStrings[collationStringsCount] = { + "default", + "big5han", + "dict", + "direct", + "gb2312", + "phonebk", + "pinyin", + "phonetic", + "reformed", + "standard", + "stroke", + "trad", + "unihan" +}; + +static const int casePreferenceStringsCount = 3; +static const char * const casePreferenceStrings[casePreferenceStringsCount] = { + "IgnoreCase", + "PreferUpperCase", + "PreferLowerCase" +}; + +#ifndef NO_COLLATION_SUPPORT +JsonDbCollator::Collation _q_correctCollationString(const QString &s) +{ + for (int i = 0; i < collationStringsCount; ++i) { + if (s == collationStrings[i]) + return JsonDbCollator::Collation(i); + } + return JsonDbCollator::Default; +} +JsonDbCollator::CasePreference _q_correctCasePreferenceString(const QString &s) +{ + for (int i = 0; i < casePreferenceStringsCount; ++i) { + if (s == casePreferenceStrings[i]) + return JsonDbCollator::CasePreference(i); + } + return JsonDbCollator::IgnoreCase; +} +#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 + +QString _q_bytesToHexString(const QByteArray &ba) +{ + static const ushort digits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + + uint len = ba.size(); + const char *data = ba.constData(); + uint idx = 0; + + QString result(len*2, Qt::Uninitialized); + QChar *resultData = result.data(); + + for (uint i = 0; i < len; ++i, idx += 2) { + uint j = (data[i] >> 4) & 0xf; + resultData[idx] = QChar(digits[j]); + j = data[i] & 0xf; + resultData[idx+1] = QChar(digits[j]); + } + + return result; +} + +class JsonDbIndexCursor +{ +public: + JsonDbIndexCursor(JsonDbIndex *index); + ~JsonDbIndexCursor(); + + bool seek(const QJsonValue &value); + bool seekRange(const QJsonValue &value); + + bool first(); + bool current(QJsonValue &key, ObjectKey &value); + bool currentKey(QJsonValue &key); + bool currentValue(ObjectKey &value); + bool next(); + bool prev(); + +private: + bool isOwnTransaction; + JsonDbBtree::Transaction *mTxn; + JsonDbBtree::Cursor mCursor; + JsonDbIndex *mIndex; + + JsonDbIndexCursor(const JsonDbIndexCursor&); +}; + +JsonDbIndex::JsonDbIndex(const QString &fileName, const QString &indexName, const QString &propertyName, + 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) + , mCaseSensitivity(caseSensitivity) +#ifndef NO_COLLATION_SUPPORT + , mCollator(JsonDbCollator(QLocale(locale), _q_correctCollationString(collation))) +#endif + , mStateNumber(0) + , mBdb(0) + , mScriptEngine(0) + , mCacheSize(0) +{ + QFileInfo fi(fileName); + QString dirName = fi.dir().path(); + QString baseName = fi.fileName(); + if (baseName.endsWith(".db")) + baseName.chop(3); + mFileName = QString("%1/%2-%3-Index.db").arg(dirName).arg(baseName).arg(indexName); +#ifndef NO_COLLATION_SUPPORT + mCollator.setCasePreference(_q_correctCasePreferenceString(mCasePreference)); +#endif +} + +JsonDbIndex::~JsonDbIndex() +{ + if (mBdb) { + close(); + mBdb.reset(); + } +} + +bool JsonDbIndex::setPropertyFunction(const QString &propertyFunction) +{ + if (!mScriptEngine) + mScriptEngine = JsonDbScriptEngine::scriptEngine(); + + // for "emit" + JsonDbJoinProxy *mapProxy = new JsonDbJoinProxy(0, 0, this); + connect(mapProxy, SIGNAL(viewObjectEmitted(QJSValue)), + this, SLOT(propertyValueEmitted(QJSValue))); + QString proxyName(QString("_jsondbIndexProxy%1").arg(mIndexName)); + proxyName.replace(".", "$"); + mScriptEngine->globalObject().setProperty(proxyName, mScriptEngine->newQObject(mapProxy)); + + QString script(QString("(function() { var jsondb={emit: %2.create, lookup: %2.lookup }; var fcn = (%1); return fcn})()").arg(propertyFunction).arg(proxyName)); + mPropertyFunction = mScriptEngine->evaluate(script); + if (mPropertyFunction.isError() || !mPropertyFunction.isCallable()) { + qDebug() << "Unable to parse index value function: " << mPropertyFunction.toString(); + return false; + } + + return true; +} + +bool JsonDbIndex::open() +{ + if (mPropertyName == JsonDbString::kUuidStr) + return true; + + mBdb.reset(new JsonDbBtree()); + + if (mCacheSize) + mBdb->setCacheSize(mCacheSize); + + if (!mBdb->open(mFileName, JsonDbBtree::Default)) { + qCritical() << "mBdb->open" << mBdb->errorMessage(); + return false; + } + + mBdb->setCompareFunction(forwardKeyCmp); + + mStateNumber = mBdb->tag(); + if (jsondbSettings->debug() && jsondbSettings->verbose()) + qDebug() << "JsonDbIndex::open" << mStateNumber << mFileName; + return true; +} + +void JsonDbIndex::close() +{ + if (mBdb) + mBdb->close(); +} + +/*! + Returns true if the index's btree file exists. +*/ +bool JsonDbIndex::exists() const +{ + QFile file(mFileName); + return file.exists(); +} + +bool JsonDbIndex::validateIndex(const JsonDbObject &newIndex, const JsonDbObject &oldIndex, QString &message) +{ + message.clear(); + + if (!newIndex.isEmpty() && !oldIndex.isEmpty() && oldIndex.type() == JsonDbString::kIndexTypeStr) { + if (oldIndex.value(JsonDbString::kPropertyNameStr).toString() != newIndex.value(JsonDbString::kPropertyNameStr).toString()) + message = QString("Changing old index propertyName '%1' to '%2' not supported") + .arg(oldIndex.value(JsonDbString::kPropertyNameStr).toString()) + .arg(newIndex.value(JsonDbString::kPropertyNameStr).toString()); + else if (oldIndex.value(JsonDbString::kPropertyTypeStr).toString() != newIndex.value(JsonDbString::kPropertyTypeStr).toString()) + 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) != 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()); + else if (oldIndex.value(JsonDbString::kPropertyFunctionStr).toString() != newIndex.value(JsonDbString::kPropertyFunctionStr).toString()) + message = QString("Changing old index propertyFunction from '%1' to '%2' not supported") + .arg(oldIndex.value(JsonDbString::kPropertyFunctionStr).toString()) + .arg(newIndex.value(JsonDbString::kPropertyFunctionStr).toString()); + } + + if (!(newIndex.contains(JsonDbString::kPropertyFunctionStr) ^ newIndex.contains(JsonDbString::kPropertyNameStr))) + message = QString("Index object must have one of propertyName or propertyFunction set"); + else if (newIndex.contains(JsonDbString::kPropertyFunctionStr) && !newIndex.contains(JsonDbString::kNameStr)) + message = QString("Index object with propertyFunction must have name"); + + return message.isEmpty(); +} + +QString JsonDbIndex::determineName(const JsonDbObject &index) +{ + QString indexName = index.value(JsonDbString::kNameStr).toString(); + QString propertyName = index.value(JsonDbString::kPropertyNameStr).toString(); + + if (indexName.isEmpty()) + return propertyName; + return indexName; +} + +JsonDbBtree *JsonDbIndex::bdb() +{ + if (!mBdb) + open(); + return mBdb.data(); +} + +QJsonValue JsonDbIndex::indexValue(const QJsonValue &v) +{ + if (!v.isString()) + return v; + + QJsonValue result; + if (mCaseSensitivity == Qt::CaseInsensitive) + result = v.toString().toLower(); + else + result = v; + +#ifndef NO_COLLATION_SUPPORT + if (!mCollation.isEmpty() && !mLocale.isEmpty()) + result = _q_bytesToHexString(mCollator.sortKey(v.toString())); +#endif + + return result; +} + +QList<QJsonValue> JsonDbIndex::indexValues(JsonDbObject &object) +{ + mFieldValues.clear(); + if (!mScriptEngine) { + int size = mPath.size(); + if (mPath[size-1] == QString("*")) { + QJsonValue v = object.propertyLookup(mPath.mid(0, size-1)); + QJsonArray array = v.toArray(); + mFieldValues.reserve(array.size()); + for (int i = 0; i < array.size(); ++i) { + mFieldValues.append(indexValue(array.at(i))); + } + } else { + QJsonValue v = object.propertyLookup(mPath); + if (!v.isUndefined()) { + mFieldValues.append(indexValue(v)); + } + } + } else { + QJSValueList args; + args << mScriptEngine->toScriptValue(object.toVariantMap()); + mPropertyFunction.call(args); + } + return mFieldValues; +} + +void JsonDbIndex::propertyValueEmitted(QJSValue value) +{ + if (!value.isUndefined()) + mFieldValues.append(JsonDbScriptEngine::fromJSValue(value)); +} + +void JsonDbIndex::indexObject(const ObjectKey &objectKey, JsonDbObject &object, quint32 stateNumber) +{ + 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); + if (!fieldValues.size()) + return; + bool ok; + if (!mBdb) + open(); + if (!mBdb->isWriting()) + mObjectTable->begin(this); + JsonDbBtree::Transaction *txn = mBdb->writeTransaction(); + for (int i = 0; i < fieldValues.size(); i++) { + QJsonValue fieldValue = fieldValues.at(i); + fieldValue = makeFieldValue(fieldValue, mPropertyType); + if (fieldValue.isUndefined()) + continue; + QByteArray forwardKey(makeForwardKey(fieldValue, objectKey)); + QByteArray forwardValue(makeForwardValue(objectKey)); + + if (jsondbSettings->debug()) + qDebug() << "indexing" << objectKey << mPropertyName << fieldValue + << "forwardIndex" << "key" << forwardKey.toHex() + << "forwardIndex" << "value" << forwardValue.toHex() + << object; + ok = txn->put(forwardKey, forwardValue); + if (!ok) qCritical() << __FUNCTION__ << "putting fowardIndex" << mBdb->errorMessage(); + } + if (jsondbSettings->debug() && (stateNumber < mStateNumber)) + qDebug() << "JsonDbIndex::indexObject" << "stale update" << stateNumber << mStateNumber << mFileName; + mStateNumber = qMax(stateNumber, mStateNumber); + +#ifdef CHECK_INDEX_ORDERING + checkIndex() +#endif +} + +void JsonDbIndex::deindexObject(const ObjectKey &objectKey, JsonDbObject &object, quint32 stateNumber) +{ + if (mPropertyName == JsonDbString::kUuidStr) + return; + if (!mObjectType.isEmpty() && !mObjectType.contains(object.value(JsonDbString::kTypeStr).toString())) + return; + if (!mBdb) + open(); + QList<QJsonValue> fieldValues = indexValues(object); + if (!fieldValues.size()) + return; + if (!mBdb->isWriting()) + mObjectTable->begin(this); + JsonDbBtree::Transaction *txn = mBdb->writeTransaction(); + for (int i = 0; i < fieldValues.size(); i++) { + QJsonValue fieldValue = fieldValues.at(i); + fieldValue = makeFieldValue(fieldValue, mPropertyType); + if (fieldValue.isUndefined()) + continue; + if (jsondbSettings->debug()) + qDebug() << "deindexing" << objectKey << mPropertyName << fieldValue; + QByteArray forwardKey(makeForwardKey(fieldValue, objectKey)); + if (!txn->remove(forwardKey)) { + qDebug() << "deindexing failed" << objectKey << mPropertyName << fieldValue << object << forwardKey.toHex(); + } + } + if (jsondbSettings->verbose() && (stateNumber < mStateNumber)) + qDebug() << "JsonDbIndex::deindexObject" << "stale update" << stateNumber << mStateNumber << mFileName; +#ifdef CHECK_INDEX_ORDERING + checkIndex(); +#endif +} + +quint32 JsonDbIndex::stateNumber() const +{ + return mStateNumber; +} + +JsonDbBtree::Transaction *JsonDbIndex::begin() +{ + if (!mBdb) + open(); + return mBdb->beginWrite(); +} +bool JsonDbIndex::commit(quint32 stateNumber) +{ + if (mBdb->isWriting()) + mBdb->writeTransaction()->commit(stateNumber); + return false; +} +bool JsonDbIndex::abort() +{ + if (mBdb->isWriting()) + mBdb->writeTransaction()->abort(); + return true; +} +bool JsonDbIndex::clearData() +{ + return mBdb->clearData(); +} + +void JsonDbIndex::checkIndex() +{ + if (mPropertyName == JsonDbString::kUuidStr) + return; + + qDebug() << "checkIndex" << mPropertyName; + int countf = 0; + bool isInTransaction = mBdb.data()->btree()->writeTransaction(); + JsonDbBtree::Transaction *txnf = mBdb.data()->btree()->writeTransaction() ? mBdb.data()->btree()->writeTransaction() : mBdb.data()->btree()->beginWrite(); + JsonDbBtree::Cursor cursorf(txnf); + bool ok = cursorf.first(); + if (ok) { + countf++; + QByteArray outkey1; + ok = cursorf.current(&outkey1, 0); + while (cursorf.next()) { + countf++; + QByteArray outkey2; + cursorf.current(&outkey2, 0); + //qDebug() << outkey1.toHex() << outkey2.toHex(); + if (memcmp(outkey1.constData(), outkey2.constData(), outkey1.size()) >= 0) { + qDebug() << "out of order index" << mPropertyName << endl + << outkey1.toHex() << endl + << outkey2.toHex() << endl; + } + outkey1 = outkey2; + } + } + if (!isInTransaction) + txnf->abort(); + + qDebug() << "checkIndex" << mPropertyName << "reversed"; + // now check other direction + int countr = 0; + isInTransaction = mBdb.data()->btree()->writeTransaction(); + JsonDbBtree::Transaction *txnr = mBdb.data()->btree()->writeTransaction() ? mBdb.data()->btree()->writeTransaction() : mBdb.data()->btree()->beginWrite(); + JsonDbBtree::Cursor cursorr(txnr); + ok = cursorr.last(); + if (ok) { + countr++; + QByteArray outkey1; + ok = cursorr.current(&outkey1, 0); + while (cursorr.previous()) { + countr++; + QByteArray outkey2; + cursorr.current(&outkey2, 0); + //qDebug() << outkey1.toHex() << outkey2.toHex(); + if (memcmp(outkey1.constData(), outkey2.constData(), outkey1.size()) <= 0) { + qDebug() << "reverse walk: out of order index" << mPropertyName << endl + << outkey1.toHex() << endl + << outkey2.toHex() << endl; + } + outkey1 = outkey2; + } + } + if (!isInTransaction) + txnr->abort(); + qDebug() << "checkIndex" << mPropertyName << "done" << countf << countr << "entries checked"; + +} + +void JsonDbIndex::setCacheSize(quint32 cacheSize) +{ + mCacheSize = cacheSize; + if (mBdb) + mBdb->setCacheSize(cacheSize); +} + +JsonDbIndexCursor::JsonDbIndexCursor(JsonDbIndex *index) + : isOwnTransaction(!index->bdb()->writeTransaction()), + mTxn(isOwnTransaction ? index->bdb()->beginWrite() : index->bdb()->writeTransaction()), + mCursor(mTxn) +{ +} + +JsonDbIndexCursor::~JsonDbIndexCursor() +{ + if (isOwnTransaction) + mTxn->abort(); +} + +bool JsonDbIndexCursor::seek(const QJsonValue &value) +{ + QByteArray forwardKey(makeForwardKey(makeFieldValue(value, mIndex->propertyType()), ObjectKey())); + return mCursor.seek(forwardKey); +} + +bool JsonDbIndexCursor::seekRange(const QJsonValue &value) +{ + QByteArray forwardKey(makeForwardKey(makeFieldValue(value, mIndex->propertyType()), ObjectKey())); + return mCursor.seekRange(forwardKey); +} + +bool JsonDbIndexCursor::current(QJsonValue &key, ObjectKey &value) +{ + QByteArray baKey, baValue; + if (!mCursor.current(&baKey, &baValue)) + return false; + forwardKeySplit(baKey, key); + forwardValueSplit(baValue, value); + return true; +} + +bool JsonDbIndexCursor::currentKey(QJsonValue &key) +{ + QByteArray baKey; + if (!mCursor.current(&baKey, 0)) + return false; + forwardKeySplit(baKey, key); + return true; +} + +bool JsonDbIndexCursor::currentValue(ObjectKey &value) +{ + QByteArray baValue; + if (!mCursor.current(0, &baValue)) + return false; + forwardValueSplit(baValue, value); + return true; +} + +bool JsonDbIndexCursor::first() +{ + return mCursor.first(); +} + +bool JsonDbIndexCursor::next() +{ + return mCursor.next(); +} + +bool JsonDbIndexCursor::prev() +{ + return mCursor.previous(); +} + +#include "moc_jsondbindex.cpp" + +QT_END_NAMESPACE_JSONDB_PARTITION diff --git a/src/partition/jsondbindex.h b/src/partition/jsondbindex.h new file mode 100644 index 00000000..c892723b --- /dev/null +++ b/src/partition/jsondbindex.h @@ -0,0 +1,154 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef JSONDB_INDEX_H +#define JSONDB_INDEX_H + +#include <QObject> +#include <QJSEngine> +#include <QPointer> +#include <QStringList> + +#include <qjsonarray.h> +#include <qjsonobject.h> +#include <qjsonvalue.h> + +#include "jsondbobject.h" + +#include "jsondbpartitionglobal.h" +#include "jsondbobjectkey.h" +#include "jsondbbtree.h" +#include "jsondbcollator.h" + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE_JSONDB_PARTITION + +class JsonDbPartition; +class JsonDbObjectTable; + +class Q_JSONDB_PARTITION_EXPORT JsonDbIndex : public QObject +{ + Q_OBJECT +public: + JsonDbIndex(const QString &fileName, const QString &indexName, const QString &propertyName, + const QString &propertyType, const QStringList &objectType, const QString &locale, const QString &collation, + const QString &casePreference, Qt::CaseSensitivity caseSensitivity, + JsonDbObjectTable *objectTable); + ~JsonDbIndex(); + + QString propertyName() const { return mPropertyName; } + QStringList fieldPath() const { return mPath; } + QString propertyType() const { return mPropertyType; } + QStringList objectType() const { return mObjectType; } + + JsonDbBtree *bdb(); + + bool setPropertyFunction(const QString &propertyFunction); + void indexObject(const ObjectKey &objectKey, JsonDbObject &object, quint32 stateNumber); + void deindexObject(const ObjectKey &objectKey, JsonDbObject &object, quint32 stateNumber); + QList<QJsonValue> indexValues(JsonDbObject &object); + + quint32 stateNumber() const; + + JsonDbBtree::Transaction *begin(); + bool commit(quint32); + bool abort(); + bool clearData(); + + void checkIndex(); + void setCacheSize(quint32 cacheSize); + bool open(); + void close(); + bool exists() const; + + static bool validateIndex(const JsonDbObject &newIndex, const JsonDbObject &oldIndex, QString &message); + static QString determineName(const JsonDbObject &index); + +private: + QJsonValue indexValue(const QJsonValue &v); + +private slots: + void propertyValueEmitted(QJSValue); + +private: + JsonDbObjectTable *mObjectTable; + QString mFileName; + QString mIndexName; + QString mPropertyName; + QStringList mPath; + QString mPropertyType; + QStringList mObjectType; + QString mLocale; + QString mCollation; + QString mCasePreference; + Qt::CaseSensitivity mCaseSensitivity; +#ifndef NO_COLLATION_SUPPORT + JsonDbCollator mCollator; +#endif + quint32 mStateNumber; + QScopedPointer<JsonDbBtree> mBdb; + QJSEngine *mScriptEngine; + QJSValue mPropertyFunction; + QList<QJsonValue> mFieldValues; + quint32 mCacheSize; +}; + +class IndexSpec { +public: + QString name; + QString propertyName; + QStringList path; + QString propertyType; + QString locale; + QString collation; + QString casePreference; + Qt::CaseSensitivity caseSensitivity; + QStringList objectType; + bool lazy; + QPointer<JsonDbIndex> index; +}; + +QT_END_NAMESPACE_JSONDB_PARTITION + +QT_END_HEADER + +#endif // JSONDB_INDEX_H diff --git a/src/partition/jsondbindexquery.cpp b/src/partition/jsondbindexquery.cpp new file mode 100644 index 00000000..02086b1b --- /dev/null +++ b/src/partition/jsondbindexquery.cpp @@ -0,0 +1,448 @@ +/**************************************************************************** +** +** 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 "jsondbindex.h" +#include "jsondbindexquery.h" +#include "jsondbobjecttable.h" +#include "jsondbpartition.h" +#include "jsondbsettings.h" +#include "qbtree.h" +#include "qbtreecursor.h" +#include "qbtreetxn.h" + +#include <QJsonDocument> + +QT_BEGIN_NAMESPACE_JSONDB_PARTITION + +JsonDbIndexQuery *JsonDbIndexQuery::indexQuery(JsonDbPartition *partition, JsonDbObjectTable *table, + const QString &propertyName, const QString &propertyType, + const JsonDbOwner *owner, bool ascending) +{ + if (propertyName == JsonDbString::kUuidStr) + return new JsonDbUuidQuery(partition, table, propertyName, owner, ascending); + else + return new JsonDbIndexQuery(partition, table, propertyName, propertyType, owner, ascending); +} + +JsonDbUuidQuery::JsonDbUuidQuery(JsonDbPartition *partition, JsonDbObjectTable *table, const QString &propertyName, const JsonDbOwner *owner, bool ascending) + : JsonDbIndexQuery(partition, table, propertyName, QString(), owner, ascending) +{ +} + +JsonDbIndexQuery::JsonDbIndexQuery(JsonDbPartition *partition, JsonDbObjectTable *table, + const QString &propertyName, const QString &propertyType, + const JsonDbOwner *owner, bool ascending) + : mPartition(partition) + , mObjectTable(table) + , mBdbIndex(0) + , mCursor(0) + , mOwner(owner) + , mMin(QJsonValue::Undefined) + , mMax(QJsonValue::Undefined) + , mAscending(ascending) + , mPropertyName(propertyName) + , mPropertyType(propertyType) + , mSparseMatchPossible(false) + , mResidualQuery(0) +{ + if (propertyName != JsonDbString::kUuidStr) { + mBdbIndex = table->indexSpec(propertyName)->index->bdb(); + isOwnTransaction = !mBdbIndex->writeTransaction(); + mTxn = isOwnTransaction ? mBdbIndex->beginWrite() : mBdbIndex->writeTransaction(); + mCursor = new JsonDbBtree::Cursor(mTxn); + } else { + isOwnTransaction = !table->bdb()->writeTransaction(); + mTxn = isOwnTransaction ? table->bdb()->beginWrite() : table->bdb()->writeTransaction(); + mCursor = new JsonDbBtree::Cursor(mTxn); + } +} +JsonDbIndexQuery::~JsonDbIndexQuery() +{ + delete mResidualQuery; + if (isOwnTransaction) + mTxn->abort(); + delete mCursor; + for (int i = 0; i < mQueryConstraints.size(); i++) + delete mQueryConstraints[i]; +} + +QString JsonDbIndexQuery::partition() const +{ + return mPartition->name(); +} + +quint32 JsonDbIndexQuery::stateNumber() const +{ + return mBdbIndex->tag(); +} + +bool JsonDbIndexQuery::matches(const QJsonValue &fieldValue) +{ + for (int i = 0; i < mQueryConstraints.size(); i++) { + if (!mQueryConstraints[i]->matches(fieldValue)) + return false; + } + return true; +} + +void JsonDbIndexQuery::setMin(const QJsonValue &value) +{ + mMin = makeFieldValue(value, mPropertyType); +} + +void JsonDbIndexQuery::setMax(const QJsonValue &value) +{ + mMax = makeFieldValue(value, mPropertyType); +} + +bool JsonDbIndexQuery::seekToStart(QJsonValue &fieldValue) +{ + QByteArray forwardKey; + if (mAscending) { + forwardKey = makeForwardKey(mMin, ObjectKey()); + if (jsondbSettings->debugQuery()) + qDebug() << __FUNCTION__ << __LINE__ << "mMin" << mMin << "key" << forwardKey.toHex(); + } else { + forwardKey = makeForwardKey(mMax, ObjectKey()); + if (jsondbSettings->debugQuery()) + qDebug() << __FUNCTION__ << __LINE__ << "mMax" << mMin << "key" << forwardKey.toHex(); + } + + bool ok = false; + if (mAscending) { + if (!mMin.isUndefined()) { + ok = mCursor->seekRange(forwardKey); + if (jsondbSettings->debugQuery()) + qDebug() << "IndexQuery::first" << __LINE__ << "ok after seekRange" << ok; + } + if (!ok) { + ok = mCursor->first(); + } + } else { + // need a seekDescending + ok = mCursor->last(); + } + if (ok) { + QByteArray baKey; + mCursor->current(&baKey, 0); + forwardKeySplit(baKey, fieldValue); + } + //qDebug() << "IndexQuery::seekToStart" << (mAscending ? mMin : mMax) << "ok" << ok << fieldValue; + return ok; +} + +bool JsonDbIndexQuery::seekToNext(QJsonValue &fieldValue) +{ + bool ok = mAscending ? mCursor->next() : mCursor->previous(); + if (ok) { + QByteArray baKey; + mCursor->current(&baKey, 0); + forwardKeySplit(baKey, fieldValue); + } + //qDebug() << "IndexQuery::seekToNext" << "ok" << ok << fieldValue; + return ok; +} + +JsonDbObject JsonDbIndexQuery::currentObjectAndTypeNumber(ObjectKey &objectKey) +{ + QByteArray baValue; + mCursor->current(0, &baValue); + forwardValueSplit(baValue, objectKey); + + if (jsondbSettings->debugQuery()) + qDebug() << __FILE__ << __LINE__ << "objectKey" << objectKey << baValue.toHex(); + JsonDbObject object; + mObjectTable->get(objectKey, &object); + return object; +} + +quint32 JsonDbUuidQuery::stateNumber() const +{ + return mObjectTable->stateNumber(); +} + +bool JsonDbUuidQuery::seekToStart(QJsonValue &fieldValue) +{ + bool ok; + if (mAscending) { + if (!mMin.isUndefined()) { + ObjectKey objectKey(mMin.toString()); + ok = mCursor->seekRange(objectKey.toByteArray()); + } else { + ok = mCursor->first(); + } + } else { + if (!mMax.isUndefined()) { + ObjectKey objectKey(mMax.toString()); + ok = mCursor->seekRange(objectKey.toByteArray()); + } else { + ok = mCursor->last(); + } + } + QByteArray baKey; + while (ok) { + mCursor->current(&baKey, 0); + if (baKey.size() == 16) + break; + if (mAscending) + ok = mCursor->next(); + else + ok = mCursor->previous(); + } + if (ok) { + QUuid quuid(QUuid::fromRfc4122(baKey)); + ObjectKey objectKey(quuid); + fieldValue = objectKey.key.toString(); + } + return ok; +} + +bool JsonDbUuidQuery::seekToNext(QJsonValue &fieldValue) +{ + bool ok = mAscending ? mCursor->next() : mCursor->previous(); + QByteArray baKey; + while (ok) { + mCursor->current(&baKey, 0); + if (baKey.size() == 16) + break; + if (mAscending) + ok = mCursor->next(); + else + ok = mCursor->previous(); + } + if (ok) { + QUuid quuid(QUuid::fromRfc4122(baKey)); + ObjectKey objectKey(quuid); + fieldValue = objectKey.key.toString(); + } + return ok; +} + +JsonDbObject JsonDbUuidQuery::currentObjectAndTypeNumber(ObjectKey &objectKey) +{ + QByteArray baKey, baValue; + mCursor->current(&baKey, &baValue); + objectKey = ObjectKey(baKey); + + if (jsondbSettings->debugQuery()) + qDebug() << __FILE__ << __LINE__ << "objectKey" << objectKey << baKey.toHex(); + JsonDbObject object(QJsonDocument::fromBinaryData(baValue).object()); + return object; +} + +void JsonDbIndexQuery::setResultExpressionList(const QStringList &resultExpressionList) +{ + mResultExpressionList = resultExpressionList; + int numExpressions = resultExpressionList.size(); + mJoinPaths.resize(numExpressions); + for (int i = 0; i < numExpressions; i++) { + const QString &propertyName = resultExpressionList.at(i); + QStringList joinPath = propertyName.split("->"); + int joinPathSize = joinPath.size(); + QVector<QStringList> fieldPaths(joinPathSize); + for (int j = 0; j < joinPathSize; j++) { + QString joinField = joinPath[j]; + fieldPaths[j] = joinField.split('.'); + } + mJoinPaths[i] = fieldPaths; + } +} + +JsonDbObject JsonDbIndexQuery::first() +{ + mSparseMatchPossible = false; + for (int i = 0; i < mQueryConstraints.size(); i++) { + mSparseMatchPossible |= mQueryConstraints[i]->sparseMatchPossible(); + } + + QJsonValue fieldValue; + bool ok = seekToStart(fieldValue); + if (jsondbSettings->debugQuery()) + qDebug() << "IndexQuery::first" << __LINE__ << "ok after first/last()" << ok; + for (; ok; ok = seekToNext(fieldValue)) { + mFieldValue = fieldValue; + if (jsondbSettings->debugQuery()) + qDebug() << "IndexQuery::first()" + << "mPropertyName" << mPropertyName + << "fieldValue" << fieldValue + << (mAscending ? "ascending" : "descending"); + + if (jsondbSettings->debugQuery()) + qDebug() << "IndexQuery::first()" << "matches(fieldValue)" << matches(fieldValue); + + if (!matches(fieldValue)) + continue; + + ObjectKey objectKey; + JsonDbObject object(currentObjectAndTypeNumber(objectKey)); + if (jsondbSettings->debugQuery()) + qDebug() << "IndexQuery::first()" << __LINE__ << "objectKey" << objectKey << object.value(JsonDbString::kDeletedStr).toBool(); + if (object.contains(JsonDbString::kDeletedStr) && object.value(JsonDbString::kDeletedStr).toBool()) + continue; + + if (!mTypeNames.isEmpty() && !mTypeNames.contains(object.value(JsonDbString::kTypeStr).toString())) + continue; + if (jsondbSettings->debugQuery()) + qDebug() << "mTypeName" << mTypeNames << "!contains" << object << "->" << object.value(JsonDbString::kTypeStr); + + if (mResidualQuery && !mResidualQuery->match(object, &mObjectCache, mPartition)) + continue; + + if (jsondbSettings->debugQuery()) + qDebug() << "IndexQuery::first()" << "returning objectKey" << objectKey; + + return object; + } + mUuid.clear(); + return QJsonObject(); +} + +JsonDbObject JsonDbIndexQuery::next() +{ + QJsonValue fieldValue; + while (seekToNext(fieldValue)) { + mFieldValue = fieldValue; + if (jsondbSettings->debugQuery()) { + qDebug() << "IndexQuery::next()" << "mPropertyName" << mPropertyName + << "fieldValue" << fieldValue + << (mAscending ? "ascending" : "descending"); + qDebug() << "IndexQuery::next()" << "matches(fieldValue)" << matches(fieldValue); + } + if (!matches(fieldValue)) { + if (mSparseMatchPossible) + continue; + else + break; + } + + ObjectKey objectKey; + JsonDbObject object(currentObjectAndTypeNumber(objectKey)); + if (object.contains(JsonDbString::kDeletedStr) && object.value(JsonDbString::kDeletedStr).toBool()) + continue; + + if (!mTypeNames.isEmpty() && !mTypeNames.contains(object.value(JsonDbString::kTypeStr).toString())) + continue; + + if (jsondbSettings->debugQuery()) + qDebug() << "IndexQuery::next()" << "objectKey" << objectKey; + + if (mResidualQuery && !mResidualQuery->match(object, &mObjectCache, mPartition)) + continue; + + return object; + } + mUuid.clear(); + return QJsonObject(); +} + +JsonDbObject JsonDbIndexQuery::resultObject(const JsonDbObject &object) +{ + int numExpressions = mResultExpressionList.length(); + QJsonObject result(object); + + // insert the computed index value + result.insert(QLatin1String("_indexValue"), mFieldValue); + + if (!numExpressions) + return result; + + for (int i = 0; i < numExpressions; i++) { + QJsonValue v; + + QVector<QStringList> &joinPath = mJoinPaths[i]; + int joinPathSize = joinPath.size(); + JsonDbObject baseObject(result); + for (int j = 0; j < joinPathSize-1; j++) { + QJsonValue uuidQJsonValue = baseObject.propertyLookup(joinPath[j]).toString(); + QString uuid = uuidQJsonValue.toString(); + if (uuid.isEmpty()) { + baseObject = JsonDbObject(); + } else if (mObjectCache.contains(uuid)) { + baseObject = mObjectCache.value(uuid); + } else { + ObjectKey objectKey(uuid); + bool gotBaseObject = mPartition->getObject(objectKey, baseObject); + if (gotBaseObject) + mObjectCache.insert(uuid, baseObject); + } + } + v = baseObject.propertyLookup(joinPath[joinPathSize-1]); + result.insert(mResultKeyList[i], v); + } + + return result; +} + +bool JsonDbIndexQuery::lessThan(const QJsonValue &a, const QJsonValue &b) +{ + if (a.type() == b.type()) { + if (a.type() == QJsonValue::Double) { + return a.toDouble() < b.toDouble(); + } else if (a.type() == QJsonValue::String) { + return a.toString() < b.toString(); + } else if (a.type() == QJsonValue::Bool) { + return a.toBool() < b.toBool(); + } else { + return false; + } + } else { + return false; + } +} + +bool JsonDbIndexQuery::greaterThan(const QJsonValue &a, const QJsonValue &b) +{ + if (a.type() == b.type()) { + if (a.type() == QJsonValue::Double) { + return a.toDouble() > b.toDouble(); + } else if (a.type() == QJsonValue::String) { + return a.toString() > b.toString(); + } else if (a.type() == QJsonValue::Bool) { + return a.toBool() > b.toBool(); + } else { + return false; + } + } else { + return false; + } +} + +QT_END_NAMESPACE_JSONDB_PARTITION diff --git a/src/partition/jsondbindexquery.h b/src/partition/jsondbindexquery.h new file mode 100644 index 00000000..66845445 --- /dev/null +++ b/src/partition/jsondbindexquery.h @@ -0,0 +1,249 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef JSONDB_INDEXQUERY_H +#define JSONDB_INDEXQUERY_H + +#include <QJsonValue> +#include <QRegExp> +#include <QSet> +#include <QVector> +#include <QStringList> + +#include "jsondbpartitionglobal.h" +#include "jsondbobject.h" +#include "jsondbobjectkey.h" +#include "jsondbbtree.h" + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE_JSONDB_PARTITION + +class JsonDbObjectTable; +class JsonDbOwner; +class JsonDbPartition; +class JsonDbQuery; + +class QueryConstraint { +public: + virtual ~QueryConstraint() { } + virtual bool matches(const QJsonValue &value) = 0; + virtual bool sparseMatchPossible() const { return false; } +}; + +class Q_JSONDB_PARTITION_EXPORT JsonDbIndexQuery { +protected: + JsonDbIndexQuery(JsonDbPartition *partition, JsonDbObjectTable *table, + const QString &propertyName, const QString &propertyType, + const JsonDbOwner *owner, bool ascending = true); +public: + static JsonDbIndexQuery *indexQuery(JsonDbPartition *partition, JsonDbObjectTable *table, + const QString &propertyName, const QString &propertyType, + const JsonDbOwner *owner, bool ascending = true); + ~JsonDbIndexQuery(); + + JsonDbObjectTable *objectTable() const { return mObjectTable; } + QString partition() const; + void addConstraint(QueryConstraint *qc) { mQueryConstraints.append(qc); } + bool ascending() const { return mAscending; } + QString propertyName() const { return mPropertyName; } + void setTypeNames(const QSet<QString> typeNames) { mTypeNames = typeNames; } + void setMin(const QJsonValue &minv); + void setMax(const QJsonValue &maxv); + QString aggregateOperation() const { return mAggregateOperation; } + void setAggregateOperation(QString op) { mAggregateOperation = op; } + void setResultExpressionList(const QStringList &resultExpressionList); + void setResultKeyList(QStringList resultKeyList) { mResultKeyList = resultKeyList; } + + JsonDbObject first(); // returns first matching object + JsonDbObject next(); // returns next matching object + bool matches(const QJsonValue &value); + QJsonValue fieldValue() const { return mFieldValue; } + JsonDbQuery *residualQuery() const { return mResidualQuery; } + void setResidualQuery(JsonDbQuery *residualQuery) { mResidualQuery = residualQuery; } + virtual quint32 stateNumber() const; + + JsonDbObject resultObject(const JsonDbObject &object); + + static bool lessThan(const QJsonValue &a, const QJsonValue &b); + static bool greaterThan(const QJsonValue &a, const QJsonValue &b); + +protected: + virtual bool seekToStart(QJsonValue &fieldValue); + virtual bool seekToNext(QJsonValue &fieldValue); + virtual JsonDbObject currentObjectAndTypeNumber(ObjectKey &objectKey); + +protected: + JsonDbPartition *mPartition; + JsonDbObjectTable *mObjectTable; + JsonDbBtree *mBdbIndex; + bool isOwnTransaction; + JsonDbBtree::Transaction *mTxn; + JsonDbBtree::Cursor *mCursor; + const JsonDbOwner *mOwner; + QJsonValue mMin, mMax; + QSet<QString> mTypeNames; + bool mAscending; + QString mUuid; + QVector<QueryConstraint*> mQueryConstraints; + QString mAggregateOperation; + QString mPropertyName; + QString mPropertyType; + QJsonValue mFieldValue; // value of field for the object the cursor is pointing at + bool mSparseMatchPossible; + QHash<QString, JsonDbObject> mObjectCache; + QStringList mResultExpressionList; + QStringList mResultKeyList; + QVector<QVector<QStringList> > mJoinPaths; + JsonDbQuery *mResidualQuery; +}; + +class JsonDbUuidQuery : public JsonDbIndexQuery { +protected: + JsonDbUuidQuery(JsonDbPartition *partition, JsonDbObjectTable *table, const QString &propertyName, const JsonDbOwner *owner, bool ascending = true); + virtual bool seekToStart(QJsonValue &fieldValue); + virtual bool seekToNext(QJsonValue &fieldValue); + virtual JsonDbObject currentObjectAndTypeNumber(ObjectKey &objectKey); + virtual quint32 stateNumber() const; + friend class JsonDbIndexQuery; +}; + +class QueryConstraintGt: public QueryConstraint { +public: + QueryConstraintGt(const QJsonValue &v) { mValue = v; } + inline bool matches(const QJsonValue &v) { return JsonDbIndexQuery::greaterThan(v, mValue); } +private: + QJsonValue mValue; +}; +class QueryConstraintGe: public QueryConstraint { +public: + QueryConstraintGe(const QJsonValue &v) { mValue = v; } + inline bool matches(const QJsonValue &v) { return JsonDbIndexQuery::greaterThan(v, mValue) || (v == mValue); } +private: + QJsonValue mValue; +}; +class QueryConstraintLt: public QueryConstraint { +public: + QueryConstraintLt(const QJsonValue &v) { mValue = v; } + inline bool matches(const QJsonValue &v) { return JsonDbIndexQuery::lessThan(v, mValue); } +private: + QJsonValue mValue; +}; +class QueryConstraintLe: public QueryConstraint { +public: + QueryConstraintLe(const QJsonValue &v) { mValue = v; } + inline bool matches(const QJsonValue &v) { return JsonDbIndexQuery::lessThan(v, mValue) || (v == mValue); } +private: + QJsonValue mValue; +}; +class QueryConstraintEq: public QueryConstraint { +public: + QueryConstraintEq(const QJsonValue &v) { mValue = v; } + inline bool matches(const QJsonValue &v) { return v == mValue; } +private: + QJsonValue mValue; +}; +class QueryConstraintNe: public QueryConstraint { +public: + QueryConstraintNe(const QJsonValue &v) { mValue = v; } + inline bool sparseMatchPossible() const { return true; } + inline bool matches(const QJsonValue &v) { return v != mValue; } +private: + QJsonValue mValue; +}; +class QueryConstraintExists: public QueryConstraint { +public: + QueryConstraintExists() { } + inline bool matches(const QJsonValue &v) { return !v.isUndefined(); } +}; +class QueryConstraintNotExists: public QueryConstraint { +public: + QueryConstraintNotExists() { } + // this will never match + inline bool matches(const QJsonValue &v) { return v.isUndefined(); } +}; +class QueryConstraintIn: public QueryConstraint { +public: + QueryConstraintIn(const QJsonValue &v) { mList = v.toArray();} + inline bool sparseMatchPossible() const { return true; } + inline bool matches(const QJsonValue &v) { return mList.contains(v); } +private: + QJsonArray mList; +}; +class QueryConstraintNotIn: public QueryConstraint { +public: + QueryConstraintNotIn(const QJsonValue &v) { mList = v.toArray();} + inline bool sparseMatchPossible() const { return true; } + inline bool matches(const QJsonValue &v) { return !mList.contains(v); } +private: + QJsonArray mList; +}; +class QueryConstraintContains: public QueryConstraint { +public: + QueryConstraintContains(const QJsonValue &v) { mValue = v;} + inline bool sparseMatchPossible() const { return true; } + inline bool matches(const QJsonValue &v) { return v.toArray().contains(mValue); } +private: + QJsonValue mValue; +}; +class QueryConstraintStartsWith: public QueryConstraint { +public: + QueryConstraintStartsWith(const QString &v) { mValue = v;} + inline bool sparseMatchPossible() const { return true; } + inline bool matches(const QJsonValue &v) { return (v.type() == QJsonValue::String) && v.toString().startsWith(mValue); } +private: + QString mValue; +}; +class QueryConstraintRegExp: public QueryConstraint { +public: + QueryConstraintRegExp(const QRegExp ®exp) : mRegExp(regexp) {} + inline bool matches(const QJsonValue &v) { return mRegExp.exactMatch(v.toString()); } + inline bool sparseMatchPossible() const { return true; } +private: + QString mValue; + QRegExp mRegExp; +}; + +QT_END_NAMESPACE_JSONDB_PARTITION + +QT_END_HEADER + +#endif // JSONDB_INDEXQUERY_H diff --git a/src/partition/jsondbmapdefinition.cpp b/src/partition/jsondbmapdefinition.cpp new file mode 100644 index 00000000..07c4a1df --- /dev/null +++ b/src/partition/jsondbmapdefinition.cpp @@ -0,0 +1,474 @@ +/**************************************************************************** +** +** 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 <QRegExp> +#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 "jsondbobjecttable.h" +#include "jsondbmapdefinition.h" +#include "jsondbsettings.h" +#include "jsondbscriptengine.h" +#include "jsondbview.h" + +QT_BEGIN_NAMESPACE_JSONDB_PARTITION + +JsonDbMapDefinition::JsonDbMapDefinition(const JsonDbOwner *owner, JsonDbPartition *partition, QJsonObject definition, QObject *parent) : + QObject(parent) + , mPartition(partition) + , mOwner(owner) + , mDefinition(definition) + , mScriptEngine(0) + , mUuid(definition.value(JsonDbString::kUuidStr).toString()) + , mTargetType(definition.value("targetType").toString()) + , mTargetTable(mPartition->findObjectTable(mTargetType)) +{ + mMapId = mUuid; + mMapId.replace(QRegExp("[-{}]"), "$"); + 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); + } + if (mDefinition.contains("targetKeyName")) + mTargetKeyName = mDefinition.value("targetKeyName").toString(); +} + +void JsonDbMapDefinition::definitionCreated() +{ + initScriptEngine(); + initIndexes(); + + foreach (const QString &sourceType, mSourceTypes) { + GetObjectsResult getObjectResponse = mPartition->getObjects(JsonDbString::kTypeStr, sourceType); + if (!getObjectResponse.error.isNull()) { + if (jsondbSettings->verbose()) + qDebug() << "createMapDefinition" << mSourceTypes << sourceType << mTargetType << getObjectResponse.error.toString(); + setError(getObjectResponse.error.toString()); + return; + } + JsonDbObjectList objects = getObjectResponse.data; + bool isJoin = mDefinition.contains(QLatin1String("join")); + for (int i = 0; i < objects.size(); i++) { + JsonDbObject object(objects.at(i)); + if (isJoin) + unmapObject(object); + updateObject(JsonDbObject(), objects.at(i)); + } + } +} + +void JsonDbMapDefinition::definitionRemoved(JsonDbPartition *partition, JsonDbObjectTable *table, const QString targetType, const QString &definitionUuid) +{ + // remove the output objects + GetObjectsResult getObjectResponse = table->getObjects(QLatin1String("_sourceUuids.*"), + 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 JsonDbMapDefinition::initScriptEngine() +{ + if (mScriptEngine) + return; + + mScriptEngine = JsonDbScriptEngine::scriptEngine(); + 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))); + + QString message; + bool compiled = compileMapFunctions(mScriptEngine, mDefinition, mJoinProxy, mMapFunctions, message); + if (!compiled) + setError(message); +} + +bool JsonDbMapDefinition::compileMapFunctions(QJSEngine *scriptEngine, QJsonObject definition, JsonDbJoinProxy *joinProxy, QMap<QString,QJSValue> &mapFunctions, QString &message) +{ + bool status = true; + QJsonObject sourceFunctions(definition.contains("join") + ? definition.value("join").toObject() + : definition.value("map").toObject()); + QJSValue svJoinProxyValue = joinProxy ? scriptEngine->newQObject(joinProxy) : QJSValue(QJSValue::UndefinedValue); + for (QJsonObject::const_iterator it = sourceFunctions.begin(); it != sourceFunctions.end(); ++it) { + const QString &sourceType = it.key(); + const QString &script = it.value().toString(); + QString jsonDbBinding; + if (definition.contains("join")) + // only joins can use lookup() + jsonDbBinding = QString("{ emit: proxy.create, lookup: proxy.lookup, createUuidFromString: proxy.createUuidFromString}"); + else + jsonDbBinding = QString("{ emit: proxy.create, createUuidFromString: proxy.createUuidFromString }"); + + // first, package it as a function that takes a jsondb proxy and returns the map function + QJSValue moduleFunction = + scriptEngine->evaluate(QString("(function (proxy) { var jsondb=%3; map_%1 = (%2); return map_%1; })") + .arg(QString(sourceType).replace(".", "_")) + .arg(script) + .arg(jsonDbBinding)); + if (moduleFunction.isError() || !moduleFunction.isCallable()) { + message = QString( "Unable to parse map function: " + moduleFunction.toString()); + status = false; + } + + // now pass it the jsondb proxy to get the map function + QJSValueList args; + args << svJoinProxyValue; + QJSValue mapFunction = moduleFunction.call(args); + if (moduleFunction.isError() || !moduleFunction.isCallable()) { + message = QString( "Unable to evaluate map function: " + moduleFunction.toString()); + status = false; + } + mapFunctions[sourceType] = mapFunction; + } + return status; +} + +void JsonDbMapDefinition::releaseScriptEngine() +{ + mMapFunctions.clear(); + mScriptEngine = 0; +} + + +void JsonDbMapDefinition::initIndexes() +{ + mTargetTable->addIndexOnProperty(QLatin1String("_sourceUuids.*"), QLatin1String("string"), mTargetType); +} + +void JsonDbMapDefinition::updateObject(const JsonDbObject &beforeObject, const JsonDbObject &afterObject, JsonDbUpdateList *changeList) +{ + initScriptEngine(); + QHash<QString, JsonDbObject> unmappedObjects; + mEmittedObjects.clear(); + + if (!beforeObject.isEmpty()) { + QJsonValue uuid = beforeObject.value(JsonDbString::kUuidStr); + GetObjectsResult getObjectResponse = mTargetTable->getObjects("_sourceUuids.*", uuid, mTargetType); + foreach (const JsonDbObject &unmappedObject, getObjectResponse.data) { + QString uuid = unmappedObject.value(JsonDbString::kUuidStr).toString(); + unmappedObjects[uuid] = unmappedObject; + } + } + + if (!afterObject.isDeleted()) { + if (jsondbSettings->verbose()) + qDebug() << "Mapping object" << afterObject; + mapObject(afterObject); + } + + JsonDbObjectList objectsToUpdate; + for (QHash<QString, JsonDbObject>::const_iterator it = unmappedObjects.begin(); + it != unmappedObjects.end(); + ++it) { + JsonDbObject unmappedObject = it.value(); + QString uuid = unmappedObject.value(JsonDbString::kUuidStr).toString(); + JsonDbWriteResult res; + if (mEmittedObjects.contains(uuid)) { + JsonDbObject emittedObject(mEmittedObjects.value(uuid)); + emittedObject.insert(JsonDbString::kVersionStr, unmappedObject.value(JsonDbString::kVersionStr)); + emittedObject.insert(JsonDbString::kOwnerStr, unmappedObject.value(JsonDbString::kOwnerStr)); + if (emittedObject == it.value()) + // skip duplicates + continue; + else + // update changed view objects + objectsToUpdate.append(emittedObject); + + mEmittedObjects.remove(uuid); + } else { + // remove unmatched objects + unmappedObject.markDeleted(); + if (jsondbSettings->verbose()) + qDebug() << "Unmapping object" << unmappedObject; + objectsToUpdate.append(unmappedObject); + } + } + + for (QHash<QString, JsonDbObject>::const_iterator it = mEmittedObjects.begin(); + it != mEmittedObjects.end(); + ++it) + objectsToUpdate.append(JsonDbObject(it.value())); + + JsonDbWriteResult res = mPartition->updateObjects(mOwner, objectsToUpdate, JsonDbPartition::ViewObject, changeList); + if (res.code != JsonDbError::NoError) + setError("Error creating view object: " + res.message); +} + +QJSValue JsonDbMapDefinition::mapFunction(const QString &sourceType) const +{ + if (mMapFunctions.contains(sourceType)) + return mMapFunctions[sourceType]; + else + return QJSValue(); +} + +void JsonDbMapDefinition::mapObject(JsonDbObject object) +{ + const QString &sourceType = object.value(JsonDbString::kTypeStr).toString(); + + QJSValue sv = JsonDbScriptEngine::toJSValue(object, mScriptEngine); + QString uuid = object.value(JsonDbString::kUuidStr).toString(); + mSourceUuids.clear(); + mSourceUuids.append(mUuid); // depends on the map definition object + mSourceUuids.append(uuid); // depends on the source object + QJSValue mapped; + + QJSValueList mapArgs; + mapArgs << sv; + mapped = mapFunction(sourceType).call(mapArgs); + + if (mapped.isError()) + setError("Error executing map function: " + mapped.toString()); +} + +void JsonDbMapDefinition::unmapObject(const JsonDbObject &object) +{ + Q_ASSERT(object.value(JsonDbString::kUuidStr).type() == QJsonValue::String); + QJsonValue uuid = object.value(JsonDbString::kUuidStr); + GetObjectsResult getObjectResponse = mTargetTable->getObjects("_sourceUuids.*", uuid, mTargetType); + JsonDbObjectList dependentObjects = getObjectResponse.data; + + for (int i = 0; i < dependentObjects.size(); i++) { + JsonDbObject dependentObject = dependentObjects.at(i); + if (dependentObject.value(JsonDbString::kTypeStr).toString() != mTargetType) + continue; + + dependentObject.markDeleted(); + mPartition->updateObject(mOwner, dependentObject, JsonDbPartition::ViewObject); + } +} + +void JsonDbMapDefinition::lookupRequested(const QJSValue &query, const QJSValue &context) +{ + QString objectType = query.property("objectType").toString(); + // compatibility for old style maps + if (mDefinition.value("map").isObject()) { + if (objectType.isEmpty()) { + setError("No objectType provided to jsondb.lookup"); + return; + } + if (!mSourceTypes.contains(objectType)) { + setError(QString("lookup requested for type %1 not in source types: %2") + .arg(objectType) + .arg(mSourceTypes.join(", "))); + return; + } + } + QString findKey = query.property("index").toString(); + QJSValue findValue = query.property("value"); + GetObjectsResult getObjectResponse = + mPartition->getObjects(findKey, JsonDbScriptEngine::fromJSValue(findValue), objectType, false); + if (!getObjectResponse.error.isNull()) { + if (jsondbSettings->verbose()) + qDebug() << "lookupRequested" << mSourceTypes << mTargetType + << getObjectResponse.error.toString(); + setError(getObjectResponse.error.toString()); + } + JsonDbObjectList objectList = getObjectResponse.data; + for (int i = 0; i < objectList.size(); ++i) { + JsonDbObject object = objectList.at(i); + const QString uuid = object.value(JsonDbString::kUuidStr).toString(); + if (mSourceUuids.contains(uuid)) { + if (jsondbSettings->verbose()) + qDebug() << "Lookup cycle detected" << "key" << findKey << JsonDbScriptEngine::fromJSValue(findValue) << "matching object" << uuid << "source uuids" << mSourceUuids; + continue; + } + mSourceUuids.append(uuid); + QJSValueList mapArgs; + QJSValue sv = JsonDbScriptEngine::toJSValue(object, mScriptEngine); + + mapArgs << sv << context; + QJSValue mapped = mMapFunctions[objectType].call(mapArgs); + + if (mapped.isError()) + setError("Error executing map function during lookup: " + mapped.toString()); + + mSourceUuids.removeOne(uuid); + } +} + +void JsonDbMapDefinition::viewObjectEmitted(const QJSValue &value) +{ + JsonDbObject newItem(JsonDbScriptEngine::fromJSValue(value).toObject()); + newItem.insert(JsonDbString::kTypeStr, mTargetType); + 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); +} + +bool JsonDbMapDefinition::isActive() const +{ + return !mDefinition.contains(JsonDbString::kActiveStr) || mDefinition.value(JsonDbString::kActiveStr).toBool(); +} + +void JsonDbMapDefinition::setError(const QString &errorMsg) +{ + mDefinition.insert(JsonDbString::kActiveStr, false); + mDefinition.insert(JsonDbString::kErrorStr, errorMsg); + if (mPartition) + mPartition->updateObject(mOwner, mDefinition, JsonDbPartition::ForcedWrite); +} + +bool JsonDbMapDefinition::validateDefinition(const JsonDbObject &map, JsonDbPartition *partition, QString &message) +{ + message.clear(); + QString targetType = map.value("targetType").toString(); + QString uuid = map.value(JsonDbString::kUuidStr).toString(); + JsonDbView *view = partition->findView(targetType); + QStringList sourceTypes; + + 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(); + + 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); + 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); + } + + if (map.contains("map")) + message = QLatin1Literal("Map 'join' and 'map' options are mutually exclusive"); + } else { + QJsonValue mapValue = map.value("map"); + 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()) { + QJSEngine *scriptEngine = JsonDbScriptEngine::scriptEngine(); + QMap<QString,QJSValue> mapFunctions; + compileMapFunctions(scriptEngine, map, 0, mapFunctions, message); + scriptEngine->collectGarbage(); + } + + return message.isEmpty(); +} + +#include "moc_jsondbmapdefinition.cpp" + +QT_END_NAMESPACE_JSONDB_PARTITION diff --git a/src/partition/jsondbmapdefinition.h b/src/partition/jsondbmapdefinition.h new file mode 100644 index 00000000..a25f7d7d --- /dev/null +++ b/src/partition/jsondbmapdefinition.h @@ -0,0 +1,124 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef JSONDB_MAP_DEFINITION_H +#define JSONDB_MAP_DEFINITION_H + +#include <QJSEngine> +#include <QStringList> + +#include "jsondbpartition.h" +#include "jsondbpartitionglobal.h" + +#include <qjsonarray.h> +#include <qjsonobject.h> +#include <qjsonvalue.h> + +#include <jsondbobject.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE_JSONDB_PARTITION + +class JsonDbOwner; +class JsonDbJoinProxy; +class JsonDbMapProxy; +class JsonDbObjectTable; + +class JsonDbMapDefinition : public QObject +{ + Q_OBJECT +public: + JsonDbMapDefinition(const JsonDbOwner *mOwner, JsonDbPartition *partition, QJsonObject mapDefinition, QObject *parent = 0); + QString uuid() const { return mUuid; } + QString targetType() const { return mTargetType; } + const QStringList &sourceTypes() const { return mSourceTypes; } + QString partitionName() const { return mPartition->name(); } + bool isActive() const; + QJsonObject definition() const { return mDefinition; } + QJSValue mapFunction(const QString &sourceType) const; + JsonDbObjectTable *sourceTable(const QString &sourceType) const { return mSourceTables.value(sourceType); } + const JsonDbOwner *owner() const { return mOwner; } + + static void definitionRemoved(JsonDbPartition *partition, JsonDbObjectTable *table, const QString targetType, const QString &definitionUuid); + void definitionCreated(); + + void initScriptEngine(); + void releaseScriptEngine(); + void initIndexes(); + + void setError(const QString &errorMsg); + void updateObject(const JsonDbObject &before, const JsonDbObject &after, JsonDbUpdateList *changeList = 0); + static bool validateDefinition(const JsonDbObject &map, JsonDbPartition *partition, QString &message); + static bool compileMapFunctions(QJSEngine *scriptEngine, QJsonObject definition, JsonDbJoinProxy *joinProxy, QMap<QString,QJSValue> &mapFunctions, QString &message); + +public slots: + void viewObjectEmitted(const QJSValue &value); + void lookupRequested(const QJSValue &spec, const QJSValue &context); + +private: + void mapObject(JsonDbObject object); + void unmapObject(const JsonDbObject &object); + +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; + QMap<QString,QJSValue> mMapFunctions; + QString mUuid; + QString mMapId; // uuid with special characters converted to '$' + QString mTargetType; + QStringList mSourceTypes; + JsonDbObjectTable *mTargetTable; + QMap<QString,JsonDbObjectTable *> mSourceTables; + QStringList mSourceUuids; // a set of uuids with sorted elements + QHash<QString,JsonDbObject> mEmittedObjects; +}; + +QT_END_NAMESPACE_JSONDB_PARTITION + +QT_END_HEADER + +#endif // JSONDB_MAP_DEFINITION_H diff --git a/src/partition/jsondbnotification.cpp b/src/partition/jsondbnotification.cpp new file mode 100644 index 00000000..d8687309 --- /dev/null +++ b/src/partition/jsondbnotification.cpp @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** 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 <QList> +#include <QMap> +#include <QVariantList> +#include <QString> +#include <QStringList> + +#include "jsondbnotification.h" +#include "jsondbquery.h" +#include "jsondbstrings.h" + +QT_BEGIN_NAMESPACE_JSONDB_PARTITION + +JsonDbNotification::JsonDbNotification(const JsonDbOwner *owner, const QString &uuid, const QString& query, + QStringList actions, const QString &partition) + : mOwner(owner) + , mUuid(uuid) + , mQuery(query) + , mCompiledQuery(NULL) + , mActions(None) + , mPartition(partition) + , mInitialStateNumber(0) + , mLastStateNumber(0) +{ + foreach (QString s, actions) { + if (s == JsonDbString::kCreateStr) + mActions |= Create; + else if (s == JsonDbString::kUpdateStr) + mActions |= Update; + else if ((s == "delete") || (s == JsonDbString::kRemoveStr)) + mActions |= Delete; + } +} +JsonDbNotification::~JsonDbNotification() +{ + if (mCompiledQuery) { + delete mCompiledQuery; + mCompiledQuery = 0; + } +} + +QT_END_NAMESPACE_JSONDB_PARTITION diff --git a/src/partition/jsondbnotification.h b/src/partition/jsondbnotification.h new file mode 100644 index 00000000..9e52d4bb --- /dev/null +++ b/src/partition/jsondbnotification.h @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef JSONDB_NOTIFICATION_H +#define JSONDB_NOTIFICATION_H + +#include <QObject> +#include <QJSValue> + +#include "jsondbpartitionglobal.h" + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE_JSONDB_PARTITION + +class JsonDbOwner; +class JsonDbQuery; +class Q_JSONDB_PARTITION_EXPORT JsonDbNotification { +public: + enum Action { None = 0x0000, Create = 0x0001, Update = 0x0002, Delete = 0x0004 }; + Q_DECLARE_FLAGS(Actions, Action) + + JsonDbNotification(const JsonDbOwner *owner, const QString &uuid, const QString &query, QStringList actions, const QString &partition); + ~JsonDbNotification(); + + const JsonDbOwner *owner() const { return mOwner; } + const QString& uuid() const { return mUuid; } + const QString& query() const { return mQuery; } + Actions actions() const { return mActions; } + JsonDbQuery *parsedQuery() const { return mCompiledQuery; } + void setCompiledQuery(JsonDbQuery *parsedQuery) { mCompiledQuery = parsedQuery; } + 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; + QString mQuery; + JsonDbQuery *mCompiledQuery; + Actions mActions; + QString mPartition; + quint32 mInitialStateNumber; + quint32 mLastStateNumber; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(JsonDbNotification::Actions) + +QT_END_NAMESPACE_JSONDB_PARTITION + +QT_END_HEADER + +#endif // JSONDB_NOTIFICATION_H diff --git a/src/partition/jsondbobject.cpp b/src/partition/jsondbobject.cpp new file mode 100644 index 00000000..e61d6ebf --- /dev/null +++ b/src/partition/jsondbobject.cpp @@ -0,0 +1,610 @@ +/**************************************************************************** +** +** 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 "jsondbobject.h" + +#include <QJSValue> +#include <QJSValueIterator> +#include <QStringBuilder> +#include <QStringList> +#include <QCryptographicHash> + +#include <qjsondocument.h> + +#include "jsondbstrings.h" +#include "jsondbproxy.h" + +QT_BEGIN_NAMESPACE_JSONDB_PARTITION + +JsonDbObject::JsonDbObject() +{ +} + +JsonDbObject::JsonDbObject(const QJsonObject &object) + : QJsonObject(object) +{ +} + +JsonDbObject::~JsonDbObject() +{ +} + +QByteArray JsonDbObject::toBinaryData() const +{ + return QJsonDocument(*this).toBinaryData(); +} + +QUuid JsonDbObject::uuid() const +{ + return QUuid(value(JsonDbString::kUuidStr).toString()); +} + +QString JsonDbObject::version() const +{ + return value(JsonDbString::kVersionStr).toString(); +} + +QString JsonDbObject::type() const +{ + return value(JsonDbString::kTypeStr).toString(); +} + +bool JsonDbObject::isDeleted() const +{ + QJsonValue deleted = value(JsonDbString::kDeletedStr); + + if (deleted.isUndefined() || (deleted.isBool() && deleted.toBool() == false)) + return false; + + return true; +} + +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)) { + QUuid uuid(createUuidFromString(value(idStr).toString())); + insert(JsonDbString::kUuidStr, uuid.toString()); + } else { + QUuid uuid(QUuid::createUuid()); + insert(JsonDbString::kUuidStr, uuid.toString()); + } +} + +/*! + * \brief JsonDbObject::computeVersion mostly for legacy reasons. + * Same as calling updateVersionOptimistic(*this, &versionWritten) + * + * \sa updateVersionOptimistic(), version() + * \return the new version, which is also written to the object + */ +QString JsonDbObject::computeVersion() +{ + QString versionWritten; + updateVersionOptimistic(*this, &versionWritten); + return versionWritten; +} + +/*! + * \brief JsonDbObject::updateVersionOptimistic implement an optimisticWrite + * \param other the object containing the update to be written. Do NOT call computeVersion() + * on the other object before passing it in! other._meta.history is assumed untrusted. + * \param versionWritten contains the version string of the write upon return + * \return true if the passed object is a valid write. As this version can operate + * on conflicts too, version() and versionWritten can differ. + */ +bool JsonDbObject::updateVersionOptimistic(const JsonDbObject &other, QString *versionWrittenOut) +{ + QString versionWritten; + // this is trusted and expected to contain a _meta object with book keeping info + QJsonObject meta = value(JsonDbString::kMetaStr).toObject(); + + // an array of all versions this object has replaced + QJsonArray history = meta.value(QStringLiteral("history")).toArray(); + + // all known conflicts + QJsonArray conflicts = meta.value(JsonDbString::kConflictsStr).toArray(); + + QString replacedVersion = other.version(); + + int replacedCount; + QString replacedHash = tokenizeVersion(replacedVersion, &replacedCount); + + int updateCount = replacedCount; + QString hash = replacedHash; + + // we don't trust other._meta.history, so other._version must be replacedVersion + // if other.computeVersion() was called before updateVersionOptimistic(), other can at max be a replay + // as we lost which version other is replacing. + bool isReplay = !other.computeVersion(replacedCount, replacedHash, &updateCount, &hash); + + bool isValidWrite = false; + + // first we check if this version can eliminate a conflict + for (QJsonArray::const_iterator ii = conflicts.begin(); ii < conflicts.end(); ii++) { + + JsonDbObject conflict((*ii).toObject()); + if (conflict.version() == replacedVersion) { + if (!isReplay) + conflicts.removeAt(ii.i); + if (!isValidWrite) { + addAncestor(&history, updateCount, hash); + versionWritten = versionAsString(updateCount, hash); + } + isValidWrite = true; + } + } + + // now we check if this version can progress the head + if (version() == replacedVersion) { + if (!isReplay) + *this = other; + if (!isValidWrite) + versionWritten = versionAsString(updateCount, hash); + insert(JsonDbString::kVersionStr, versionWritten); + isValidWrite = true; + } + + // make sure we can resurrect a tombstone + // Issue: Recreating a _uuid must have a updateCount higher than the tombstone + // otherwise it is considered a conflict. + if (!isValidWrite && isDeleted()) { + if (!isReplay) { + addAncestor(&history, replacedCount, replacedHash); + isReplay = false; + } + + replacedHash = tokenizeVersion(version(), &replacedCount); + updateCount = replacedCount + 1; + versionWritten = versionAsString(updateCount, hash); + + *this = other; + insert(JsonDbString::kVersionStr, versionWritten); + isValidWrite = true; + } + + // update the book keeping of what versions we have replaced in this version branch + if (isValidWrite && !isReplay) { + addAncestor(&history, replacedCount, replacedHash); + + meta = QJsonObject(); + + if (history.size()) + meta.insert(QStringLiteral("history"), history); + if (conflicts.size()) + meta.insert(JsonDbString::kConflictsStr, history); + + if (!meta.isEmpty()) + insert(JsonDbString::kMetaStr, meta); + } + + // last chance for a valid write: other is a replay from history + if (!isValidWrite && isAncestorOf(history, updateCount, hash)) { + isValidWrite = true; + versionWritten = versionAsString(updateCount, hash); + } + + if (versionWrittenOut) + *versionWrittenOut = versionWritten; + + return isValidWrite; +} + +/*! + * \brief JsonDbObject::updateVersionReplicating implements a replicatedWrite + * \param other the (remote) object to include into this one. + * \return if the passed object was a valid replication + */ +bool JsonDbObject::updateVersionReplicating(const JsonDbObject &other) +{ + // these two will be the final _meta content + QJsonArray history; + QJsonArray conflicts; + + // let's go thru all version, i.e. this, this._conflicts, other, and other._conflicts + { + // thanks to the operator <, documents will sort and remove duplicates + // the value is just for show, QSet is based on QHash, which does not sort + QMap<JsonDbObject,bool> documents; + + QUuid id = uuid(); + populateMerge(&documents, id, *this); + if (!populateMerge(&documents, id, other, true)) + return false; + + // now we have all versions sorted and duplicates removed + // let's figure out what to keep, what to toss + // this is O(n^2) but should be fine in real world situations + for (QMap<JsonDbObject,bool>::const_iterator ii = documents.begin(); ii != documents.end(); ii++) { + bool alive = !ii.key().isDeleted(); + for (QMap<JsonDbObject,bool>::const_iterator jj = ii + 1; alive && jj != documents.end(); jj++) + if (ii.key().isAncestorOf(jj.key())) + alive = false; + + if (ii+1 == documents.end()) { + // last element, so found the winner, + // assigning to *this, which is head + *this = ii.key(); + populateHistory(&history, *this, false); + } else if (alive) { + // this is a conflict, strip _meta and keep it + JsonDbObject conflict(ii.key()); + conflict.remove(JsonDbString::kMetaStr); + conflicts.append(conflict); + } else { + // this version was replaced, just keep history + populateHistory(&history, ii.key(), true); + } + } + } + + // let's write a new _meta into head + if (history.size() || conflicts.size()) { + QJsonObject meta; + if (history.size()) + meta.insert(QStringLiteral("history"), history); + if (conflicts.size()) + meta.insert(JsonDbString::kConflictsStr, conflicts); + insert(JsonDbString::kMetaStr, meta); + } else { + // this is really just for sanity reason, but it feels better to have it + // aka: this branch should never be reached in real world situations + remove(JsonDbString::kMetaStr); + } + + return true; +} + +bool JsonDbObject::populateMerge(QMap<JsonDbObject,bool> *documents, const QUuid &id, const JsonDbObject &source, bool validateSource, bool recurse) const +{ + // is this the same uuid? + bool valid = source.uuid() == id; + + if (valid && validateSource) { + // validate that the version is actually correct + int count; + QString hash = tokenizeVersion(source.version(), &count); + if (count == 0 || source.computeVersion(count, hash, 0, 0)) + valid = false; + } + + // there is source._meta.conflicts to explore + if (recurse && source.contains(JsonDbString::kMetaStr)) { + QJsonArray conflicts = source.value(JsonDbString::kMetaStr).toObject().value(JsonDbString::kConflictsStr).toArray(); + for (int ii = 0; ii < conflicts.size(); ii++) + if (!populateMerge(documents, id, conflicts.at(ii).toObject(), validateSource, false)) + valid = false; + } + + if (valid && documents) + documents->insert(source,true); + + return valid; +} + +void JsonDbObject::populateHistory(QJsonArray *history, const JsonDbObject &doc, bool includeCurrent) const +{ + QJsonArray versions = doc.value(JsonDbString::kMetaStr).toObject().value(QStringLiteral("history")).toArray(); + + for (int ii = 0; ii < versions.size(); ii++) { + QJsonValue hash = versions.at(ii); + if (hash.isString()) { + addAncestor(history, ii + 1, hash.toString()); + } else if (hash.isArray()) { + QJsonArray hashArray = hash.toArray(); + for (QJsonArray::const_iterator jj = hashArray.begin(); jj != hashArray.end(); jj++) { + if ((*jj).isString()) + addAncestor(history, ii + 1, (*jj).toString()); + } + } + } + + if (includeCurrent) { + int updateCount; + QString hash = tokenizeVersion(doc.version(), &updateCount); + addAncestor(history, updateCount, hash); + } +} + +QString JsonDbObject::tokenizeVersion(const QString &versionIn, int *updateCountOut) const +{ + int updateCount; + QString hash; + + if (versionIn.isEmpty()) { + updateCount = 0; + } else { + QStringList splitUp = versionIn.split(QChar('-')); + if (splitUp.size() == 2) { + updateCount = qMax(1, splitUp.at(0).toInt()); + hash = splitUp.at(1); + } else { + updateCount = 1; + hash = versionIn; + } + } + + if (updateCountOut) + *updateCountOut = updateCount; + + return hash; +} + +QString JsonDbObject::versionAsString(const int updateCount, const QString &hash) const +{ + return QString::number(updateCount) % QStringLiteral("-") % hash; +} + + +bool JsonDbObject::computeVersion(const int oldUpdateCount, const QString& oldHash, int *newUpdateCount, QString *newHash) const +{ + QCryptographicHash md5(QCryptographicHash::Md5); + + for (const_iterator ii = begin(); ii != end(); ii++) { + QString key = ii.key(); + if (key == JsonDbString::kUuidStr || key == JsonDbString::kVersionStr || key == JsonDbString::kMetaStr) + continue; + + md5.addData((char *) key.constData(), key.size() * 2); + + char kar = ii.value().type(); + md5.addData((char *) &kar, 1); + + switch (ii.value().type()) { + case QJsonValue::Bool: + kar = ii.value().toBool() ? '1' : '0'; + md5.addData((char *) &kar, 1); + break; + case QJsonValue::Double: { + double value = ii.value().toDouble(); + md5.addData((char *) &value, sizeof(double)); + break; + } + case QJsonValue::String: { + QString value = ii.value().toString(); + md5.addData((char *) value.constData(), value.size() * 2); + break; + } + case QJsonValue::Array: { + QJsonDocument doc(ii.value().toArray()); + int size; + const char *data = doc.rawData(&size); + md5.addData(data, size); + break; + } + case QJsonValue::Object: { + QJsonDocument doc(ii.value().toObject()); + int size; + const char *data = doc.rawData(&size); + md5.addData(data, size); + break; + } + default:; + // do nothing + } + } + + QString computedHash = QString::fromLatin1(md5.result().toHex().constData(), 10); + + if (computedHash != oldHash) { + if (newUpdateCount) + *newUpdateCount = oldUpdateCount + 1; + if (newHash) + *newHash = computedHash; + return true; + } + + return false; +} + +/*! + * \brief JsonDbObject::isAncestorOf tests if this JsonDbObject contains an ancestor version + * of the passed JsonDbObject. It does NOT take _uuid into account, it works on version() + * only. + * + * For this method to return a valid answer, the passed object needs to have an intact + * _meta object. + * + * \param other the object to check ancestorship + * \return true if this object is an ancestor version of the passed object + */ +bool JsonDbObject::isAncestorOf(const JsonDbObject &other) const +{ + QJsonArray history = other.value(JsonDbString::kMetaStr).toObject().value(QStringLiteral("history")).toArray(); + + int updateCount; + QString hash = tokenizeVersion(version(), &updateCount); + + return isAncestorOf(history, updateCount, hash); +} + +bool JsonDbObject::isAncestorOf(const QJsonArray &history, const int updateCount, const QString &hash) const +{ + if (updateCount < 1 || history.size() < updateCount) + return false; + + QJsonValue knownHashes = history.at(updateCount - 1); + if (knownHashes.isString()) + return knownHashes.toString() == hash; + else if (knownHashes.isArray()) + return knownHashes.toArray().contains(hash); + else + return false; +} + +void JsonDbObject::addAncestor(QJsonArray *history, const int updateCount, const QString &hash) const +{ + if (updateCount < 1 || !history) + return; + + int pos = updateCount - 1; + for (int ii = history->size(); ii < updateCount; ii++) + history->append(QJsonValue::Null); + + QJsonValue old = history->at(pos); + if (old.isArray()) { + QJsonArray multi = old.toArray(); + for (int ii = multi.size(); ii-- > 0;) { + QString oldHash = multi.at(ii).toString(); + if (oldHash == hash) { + return; + } else if (oldHash < hash) { + multi.insert(ii + 1, hash); + history->replace(pos, multi); + return; + } + } + multi.prepend(hash); + history->replace(pos, multi); + } else if (!old.isString()) { + history->replace(pos, hash); + } else { + QString oldHash = old.toString(); + if (oldHash == hash) + return; + + QJsonArray multi; + if (oldHash < hash) { + multi.append(oldHash); + multi.append(hash); + } else if (oldHash > hash) { + multi.append(hash); + multi.append(oldHash); + } + history->replace(pos, multi); + } +} + +/*! + * \brief JsonDbObject::operator < only operates based on version number. + * Versions are sorted by update count first, then by string comparing + * the hash. This operator does NOT sort by _uuid. + * + * \sa computeVersion(), version() + * \param other the JsonDbObject to compare it to. + * \return bool when left side is considered to be an early version + */ +bool JsonDbObject::operator <(const JsonDbObject &other) const +{ + int myCount; + QString myHash = tokenizeVersion(version(), &myCount); + int otherCount; + QString otherHash = tokenizeVersion(other.version(), &otherCount); + + if (myCount != otherCount) + return myCount < otherCount; + + return myHash < otherHash; +} + + +QJsonValue JsonDbObject::propertyLookup(const QString &path) const +{ + return propertyLookup(path.split('.')); +} + +QJsonValue JsonDbObject::propertyLookup(const QStringList &path) const +{ + if (!path.size()) { + qCritical() << "JsonDb::propertyLookup empty path"; + abort(); + return QJsonValue(QJsonValue::Undefined); + } + // TODO: one malloc here + QJsonValue value(*this); + for (int i = 0; i < path.size(); i++) { + const QString &key = path.at(i); + // this part of the property is a list + if (value.isArray()) { + QJsonArray objectList = value.toArray(); + bool ok = false; + int index = key.toInt(&ok); + if (ok && (index >= 0) && (objectList.size() > index)) + value = objectList.at(index); + else + value = QJsonValue(QJsonValue::Undefined); + } else if (value.isObject()) { + QJsonObject o = value.toObject(); + if (o.contains(key)) + value = o.value(key); + else + value = QJsonValue(QJsonValue::Undefined); + } else { + value = QJsonValue(QJsonValue::Undefined); + } + } + return value; +} + +QT_END_NAMESPACE_JSONDB_PARTITION diff --git a/src/partition/jsondbobject.h b/src/partition/jsondbobject.h new file mode 100644 index 00000000..f5860a47 --- /dev/null +++ b/src/partition/jsondbobject.h @@ -0,0 +1,114 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef JSONDB_OBJECT_H +#define JSONDB_OBJECT_H + +#include <QJSEngine> +#include <QUuid> +#include <QDebug> +#include <QVariant> + +#include <qjsonarray.h> +#include <qjsonobject.h> +#include <qjsonvalue.h> + +#include "jsondbpartitionglobal.h" + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE_JSONDB_PARTITION + +class Q_JSONDB_PARTITION_EXPORT JsonDbObject : public QJsonObject +{ +public: + JsonDbObject(); + JsonDbObject(const QJsonObject &object); + ~JsonDbObject(); + + QByteArray toBinaryData() const; + + QUuid uuid() const; + QString version() const; + QString type() const; + bool isDeleted() const; + void markDeleted(); + + void generateUuid(); + static QUuid createUuidFromString(const QString &id); + QString computeVersion(); + + bool updateVersionOptimistic(const JsonDbObject &other, QString *versionWritten); + bool updateVersionReplicating(const JsonDbObject & other); + + bool operator <(const JsonDbObject &other) const; + bool isAncestorOf(const JsonDbObject &other) const; + + QJsonValue propertyLookup(const QString &path) const; + QJsonValue propertyLookup(const QStringList &path) const; + +private: + bool populateMerge(QMap<JsonDbObject, bool> *documents, const QUuid &id, const JsonDbObject &source, bool validateSource = false, bool recurse = true) const; + void populateHistory(QJsonArray *history, const JsonDbObject &doc, bool includeCurrent) const; + QString tokenizeVersion(const QString &version, int *updateCount) const; + QString versionAsString(const int updateCount, const QString &hash) const; + + bool computeVersion(const int oldUpdateCount, const QString& oldHash, int *newUpdateCount, QString *newHash) const; + + bool isAncestorOf(const QJsonArray &history, const int updateCount, const QString &hash) const; + void addAncestor(QJsonArray *history, const int updateCount, const QString &hash) const; +}; + + +typedef QList<JsonDbObject> JsonDbObjectList; + +struct GetObjectsResult +{ + JsonDbObjectList data; + QJsonValue error; + +}; + +QT_END_NAMESPACE_JSONDB_PARTITION + +QT_END_HEADER + +#endif // JSONDB_OBJECT_H diff --git a/src/partition/jsondbobjectkey.h b/src/partition/jsondbobjectkey.h new file mode 100644 index 00000000..ca51295d --- /dev/null +++ b/src/partition/jsondbobjectkey.h @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef JSONDB_OBJECT_KEY_H +#define JSONDB_OBJECT_KEY_H + +#include <qbytearray.h> +#include <qendian.h> +#include <qdebug.h> +#include <quuid.h> + +#include "jsondbpartitionglobal.h" + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE_JSONDB_PARTITION + +class Q_JSONDB_PARTITION_EXPORT ObjectKey +{ +public: + QUuid key; // object uuid + + ObjectKey() {} + ObjectKey(const QUuid &uuid) : key(uuid) {} + ObjectKey(const QByteArray &uuid) : key(QUuid::fromRfc4122(uuid)) {} + inline bool isNull() const { return key.isNull(); } + + inline QByteArray toByteArray() const + { + return key.toRfc4122(); + } + inline bool operator==(const ObjectKey &rhs) const + { return key == rhs.key; } + + inline bool operator < (const ObjectKey &rhs) const + { return key < rhs.key; } +}; + +inline QDebug &operator<<(QDebug &qdb, const ObjectKey &objectKey) +{ + qdb << objectKey.key.toString(); + return qdb; +} + +QT_END_NAMESPACE_JSONDB_PARTITION + +QT_BEGIN_NAMESPACE + +template <> inline void qToBigEndian(QT_PREPEND_NAMESPACE_JSONDB_PARTITION(ObjectKey) src, uchar *dest) +{ + //TODO: improve me + QByteArray key = src.key.toRfc4122(); + memcpy(dest, key.constData(), key.size()); +} +template <> inline QT_PREPEND_NAMESPACE_JSONDB_PARTITION(ObjectKey) qFromBigEndian(const uchar *src) +{ + QT_PREPEND_NAMESPACE_JSONDB_PARTITION(ObjectKey) key; + key.key = QUuid::fromRfc4122(QByteArray::fromRawData((const char *)src, 16)); + return key; +} + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // JSONDB_OBJECT_KEY_H diff --git a/src/partition/jsondbobjecttable.cpp b/src/partition/jsondbobjecttable.cpp new file mode 100644 index 00000000..b5d606a0 --- /dev/null +++ b/src/partition/jsondbobjecttable.cpp @@ -0,0 +1,707 @@ +/**************************************************************************** +** +** 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 <QFileInfo> +#include <QDir> +#include <QElapsedTimer> +#include <QJsonDocument> + +#include "jsondbobjecttable.h" +#include "jsondbindex.h" +#include "jsondbstrings.h" +#include "jsondbbtree.h" +#include "jsondbobject.h" +#include "jsondbsettings.h" +#include "qbtree.h" +#include "qbtreecursor.h" +#include "qbtreetxn.h" + +QT_BEGIN_NAMESPACE_JSONDB_PARTITION + +void makeStateKey(QByteArray &baStateKey, quint32 stateNumber) +{ + baStateKey.resize(5); + char *data = baStateKey.data(); + data[4] = 'S'; + qToBigEndian(stateNumber, (uchar *)baStateKey.data()); +} + +bool isStateKey(const QByteArray &baStateKey) +{ + return (baStateKey.size() == 5) + && (baStateKey.constData()[4] == 'S'); +} + +JsonDbObjectTable::JsonDbObjectTable(JsonDbPartition *partition) : + QObject(partition) + , mPartition(partition) + , mBdb(0) +{ + mBdb = new JsonDbBtree(); +} + +JsonDbObjectTable::~JsonDbObjectTable() +{ + delete mBdb; + mBdb = 0; +} + +bool JsonDbObjectTable::open(const QString &fileName) +{ + mFilename = fileName; +#if 0 + if (!mBdb->setCmpFunc(objectKeyCmp, 0)) { + qCritical() << "mBdb->setCmpFunc" << mBdb->errorMessage(); + return false; + } +#endif + mBdb->setCacheSize(jsondbSettings->cacheSize()); + if (!mBdb->open(mFilename)) { + qCritical() << "mBdb->open" << mBdb->errorMessage(); + return false; + } + mStateNumber = mBdb->tag(); + if (jsondbSettings->verbose()) + qDebug() << "ObjectTable::open" << mStateNumber << mFilename; + return true; +} + +void JsonDbObjectTable::close() +{ + mBdb->close(); +} + +bool JsonDbObjectTable::begin() +{ + Q_ASSERT(!mBdb->isWriting()); + Q_ASSERT(mBdbTransactions.isEmpty()); + return mBdb->beginWrite() != NULL; +} + +void JsonDbObjectTable::begin(JsonDbIndex *index) +{ + if (!index->bdb()->isWriting()) + mBdbTransactions.append(index->begin()); +} + +bool JsonDbObjectTable::commit(quint32 tag) +{ + Q_ASSERT(mBdb->isWriting()); + //qDebug() << "ObjectTable::commit" << tag << mFilename; + + QByteArray baStateKey(5, 0); + makeStateKey(baStateKey, tag); + bool ok = mBdb->writeTransaction()->put(baStateKey, mStateChanges); + if (!ok) + qDebug() << "putting statekey ok" << ok << "baStateKey" << baStateKey.toHex(); + for (int i = 0; i < mStateObjectChanges.size(); ++i) { + JsonDbObject object = mStateObjectChanges.at(i); + bool ok = mBdb->writeTransaction()->put(baStateKey + object.uuid().toRfc4122(), object.toBinaryData()); + if (!ok) { + qDebug() << "putting state object ok" << ok << "baStateKey" << baStateKey.toHex() + << "object" << object; + } + } + mStateChanges.clear(); + mStateObjectChanges.clear(); + mStateNumber = tag; + + for (int i = 0; i < mBdbTransactions.size(); i++) { + JsonDbBtree::Transaction *txn = mBdbTransactions.at(i); + if (!txn->commit(tag)) { + qCritical() << __FILE__ << __LINE__ << txn->btree()->errorMessage(); + } + } + mBdbTransactions.clear(); + return mBdb->writeTransaction()->commit(tag); +} + +bool JsonDbObjectTable::abort() +{ + Q_ASSERT(mBdb->isWriting()); + mStateChanges.clear(); + mStateObjectChanges.clear(); + for (int i = 0; i < mBdbTransactions.size(); i++) { + JsonDbBtree::Transaction *txn = mBdbTransactions.at(i); + txn->abort(); + } + mBdbTransactions.clear(); + mBdb->writeTransaction()->abort(); + return true; +} + +bool JsonDbObjectTable::compact() +{ + for (QHash<QString,IndexSpec>::const_iterator it = mIndexes.begin(); + it != mIndexes.end(); + ++it) { + const IndexSpec &indexSpec = it.value(); + // _uuid index does not have bdb() because it is actually the object table itself + if (!indexSpec.index->bdb()) + continue; + if (!indexSpec.index->bdb()->compact()) + return false; + } + return mBdb->compact(); +} + +void JsonDbObjectTable::sync(JsonDbObjectTable::SyncFlags flags) +{ + if (flags & SyncObjectTable) + mBdb->sync(); + + if (flags & SyncIndexes) { + foreach (const IndexSpec &spec, mIndexes.values()) { + JsonDbIndex *index = spec.index; + if (index->bdb()) { + quint32 stateNumber = index->stateNumber(); + if (stateNumber != mStateNumber) { + index->begin(); + index->commit(mStateNumber); + } + spec.index->bdb()->sync(); + } + } + } +} + +JsonDbStat JsonDbObjectTable::stat() const +{ + JsonDbStat result; + for (QHash<QString,IndexSpec>::const_iterator it = mIndexes.begin(); + it != mIndexes.end(); + ++it) { + const IndexSpec &indexSpec = it.value(); + if (indexSpec.index->bdb()) { + JsonDbBtree::Stat stat = indexSpec.index->bdb()->btree() ? + indexSpec.index->bdb()->stats() : + JsonDbBtree::Stat(); + result += JsonDbStat(stat.reads, stat.hits, stat.writes); + } + // _uuid index does not have bdb() because it is actually the object table itself + } + JsonDbBtree::Stat stat = mBdb->btree() ? mBdb->btree()->stats() : JsonDbBtree::Stat(); + result += JsonDbStat(stat.reads, stat.hits, stat.writes); + return result; +} + + +void JsonDbObjectTable::flushCaches() +{ + for (QHash<QString,IndexSpec>::const_iterator it = mIndexes.begin(); + it != mIndexes.end(); + ++it) { + const IndexSpec &indexSpec = it.value(); + // _uuid index does not have bdb() because it is actually the object table itself + if (!indexSpec.index->bdb()) + continue; + indexSpec.index->bdb()->setCacheSize(1); + indexSpec.index->bdb()->setCacheSize(jsondbSettings->cacheSize()); + } +} + +IndexSpec *JsonDbObjectTable::indexSpec(const QString &indexName) +{ + //qDebug() << "ObjectTable::indexSpec" << propertyName << mFilename << (mIndexes.contains(propertyName) ? "exists" : "missing") << (long)this << mIndexes.keys(); + if (mIndexes.contains(indexName)) + return &mIndexes[indexName]; + else + return 0; +} + +QHash<QString, IndexSpec> JsonDbObjectTable::indexSpecs() const +{ + return mIndexes; +} + +bool JsonDbObjectTable::addIndex(const QString &indexName, const QString &propertyName, + const QString &propertyType, const QStringList &objectTypes, const QString &propertyFunction, + const QString &locale, const QString &collation, const QString &casePreference, + Qt::CaseSensitivity caseSensitivity) +{ + Q_ASSERT(propertyName.isEmpty() ^ propertyFunction.isEmpty()); + + QString name = indexName.isEmpty() ? propertyName : indexName; + //qDebug() << "ObjectTable::addIndex" << propertyName << mFilename << (mIndexes.contains(propertyName) ? "exists" : "to be created"); + if (mIndexes.contains(name)) + return true; + + //if (gVerbose) qDebug() << "ObjectTable::addIndex" << propertyName << mFilename; + + QStringList path = propertyName.split('.'); + + IndexSpec &indexSpec = mIndexes[name]; + indexSpec.name = name; + indexSpec.propertyName = propertyName; + indexSpec.path = path; + indexSpec.propertyType = propertyType; + indexSpec.locale = locale; + indexSpec.collation = collation; + indexSpec.casePreference = casePreference; + indexSpec.caseSensitivity = caseSensitivity; + indexSpec.objectType = objectTypes; + indexSpec.lazy = false; //lazy; + 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()); + bool indexExists = indexSpec.index->exists(); + indexSpec.index->open(); + + QJsonObject indexObject; + indexObject.insert(JsonDbString::kTypeStr, JsonDbString::kIndexTypeStr); + indexObject.insert(JsonDbString::kNameStr, name); + indexObject.insert(JsonDbString::kPropertyNameStr, propertyName); + indexObject.insert(JsonDbString::kPropertyTypeStr, propertyType); + indexObject.insert(JsonDbString::kLocaleStr, locale); + indexObject.insert(JsonDbString::kCollationStr, collation); + indexObject.insert(JsonDbString::kCaseSensitiveStr, (bool)caseSensitivity); + indexObject.insert(JsonDbString::kCasePreferenceStr, casePreference); + 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()); + Q_ASSERT(mIndexes.contains(name)); + + QByteArray baIndexObject; + bool needsReindexing = !indexExists; + if (indexExists && (indexSpec.index->stateNumber() != mStateNumber)) { + needsReindexing = true; + if (jsondbSettings->verbose()) + qDebug() << "Index" << name << "stateNumber" << indexSpec.index->stateNumber() << "objectTable.stateNumber" << mStateNumber << "reindexing" << "clearing"; + indexSpec.index->clearData(); + } + if (needsReindexing) + reindexObjects(name, path, stateNumber()); + + return true; +} + +bool JsonDbObjectTable::removeIndex(const QString &indexName) +{ + IndexSpec *spec = indexSpec(indexName); + if (!spec) + return false; + + QString propertyName = spec->propertyName; + if (propertyName == JsonDbString::kUuidStr || propertyName == JsonDbString::kTypeStr) + return true; + + if (spec->index) { + if (spec->index->bdb() + && spec->index->bdb()->isWriting()) { // Incase index is removed via Jdb::remove( _type=Index ) + mBdbTransactions.remove(mBdbTransactions.indexOf(spec->index->bdb()->writeTransaction())); + spec->index->abort(); + } + spec->index->close(); + if (spec->index->bdb()) + QFile::remove(spec->index->bdb()->fileName()); + delete spec->index; + mIndexes.remove(indexName); + } + + return true; +} + +void JsonDbObjectTable::reindexObjects(const QString &indexName, const QStringList &path, quint32 stateNumber) +{ + Q_ASSERT(mIndexes.contains(indexName)); + + if (jsondbSettings->verbose()) + qDebug() << "reindexObjects" << indexName << "{"; + if (indexName == JsonDbString::kUuidStr) { + return; + } + + IndexSpec &indexSpec = mIndexes[indexName]; + JsonDbIndex *index = indexSpec.index; + bool isInIndexTransaction = index->bdb()->writeTransaction(); + bool isInObjectTableTransaction = mBdb->writeTransaction(); + JsonDbBtree::Transaction *bdbTxn = mBdb->writeTransaction() ? mBdb->writeTransaction() : mBdb->beginWrite(); + JsonDbBtree::Cursor cursor(bdbTxn); + if (!isInIndexTransaction) + index->begin(); + for (bool ok = cursor.first(); ok; ok = cursor.next()) { + QByteArray baKey, baObject; + bool rok = cursor.current(&baKey, &baObject); + Q_ASSERT(rok); + if (baKey.size() != 16) // state key is 5 bytes, or history key is 5 + 16 bytes + continue; + ObjectKey objectKey(baKey); + JsonDbObject object = QJsonDocument::fromBinaryData(baObject).object(); + if (object.value(JsonDbString::kDeletedStr).toBool()) + continue; + QJsonValue fieldValue = object.propertyLookup(path); + if (!fieldValue.isNull()) + index->indexObject(objectKey, object, stateNumber); + } + if (!isInIndexTransaction) + index->commit(stateNumber); + if (!isInObjectTableTransaction) + bdbTxn->abort(); + if (jsondbSettings->verbose()) + qDebug() << "} reindexObjects"; +} + +void JsonDbObjectTable::indexObject(const ObjectKey &objectKey, JsonDbObject object, quint32 stateNumber) +{ + if (jsondbSettings->debug()) + qDebug() << "ObjectTable::indexObject" << objectKey << object.value(JsonDbString::kVersionStr).toString() << endl << mIndexes.keys(); + for (QHash<QString,IndexSpec>::const_iterator it = mIndexes.begin(); + it != mIndexes.end(); + ++it) { + Q_ASSERT(mBdb->isWriting()); + const IndexSpec &indexSpec = it.value(); + if (indexSpec.propertyName == JsonDbString::kUuidStr) + continue; + if (indexSpec.lazy) + continue; + indexSpec.index->indexObject(objectKey, object, stateNumber); + } +} + +void JsonDbObjectTable::deindexObject(const ObjectKey &objectKey, JsonDbObject object, quint32 stateNumber) +{ + if (jsondbSettings->debug()) + qDebug() << "ObjectTable::deindexObject" << objectKey << object.value(JsonDbString::kVersionStr).toString() << endl << mIndexes.keys(); + + for (QHash<QString,IndexSpec>::const_iterator it = mIndexes.begin(); + it != mIndexes.end(); + ++it) { + Q_ASSERT(mBdb->isWriting()); + const IndexSpec &indexSpec = it.value(); + if (jsondbSettings->debug()) + qDebug() << "ObjectTable::deindexObject" << indexSpec.propertyName; + if (indexSpec.propertyName == JsonDbString::kUuidStr) + continue; + if (indexSpec.lazy) + continue; + indexSpec.index->deindexObject(objectKey, object, stateNumber); + } +} + +void JsonDbObjectTable::updateIndex(JsonDbIndex *index) +{ + quint32 indexStateNumber = qMax(1u, index->bdb()->tag()); + if (indexStateNumber == stateNumber()) + return; + JsonDbUpdateList changeList; + changesSince(indexStateNumber, QSet<QString>(), &changeList); + bool inTransaction = mBdb->isWriting(); + if (!inTransaction) + index->begin(); + else if (!index->bdb()->isWriting()) { + index->begin(); + mBdbTransactions.append(index->begin()); + } + foreach (const JsonDbUpdate &change, changeList) { + JsonDbObject before = change.oldObject; + JsonDbObject after = change.newObject; + ObjectKey objectKey(after.value(JsonDbString::kUuidStr).toString()); + if (!before.isEmpty()) + index->deindexObject(objectKey, before, stateNumber()); + if (!after.isDeleted()) + index->indexObject(objectKey, after, stateNumber()); + } + if (!inTransaction) + index->commit(stateNumber()); +} + + +bool JsonDbObjectTable::get(const ObjectKey &objectKey, QJsonObject *object, bool includeDeleted) +{ + QByteArray baObjectKey(objectKey.toByteArray()); + QByteArray baObject; + bool ok = mBdb->getOne(baObjectKey, &baObject); + if (!ok) + return false; + QJsonObject o(QJsonDocument::fromBinaryData(baObject).object()); + if (!includeDeleted && o.value(JsonDbString::kDeletedStr).toBool()) + return false; + *object = o; + return true; +} + +bool JsonDbObjectTable::put(const ObjectKey &objectKey, const JsonDbObject &object) +{ + QByteArray baObjectKey(objectKey.toByteArray()); + return mBdb->putOne(baObjectKey, object.toBinaryData()); +} + +bool JsonDbObjectTable::remove(const ObjectKey &objectKey) +{ + QByteArray baObjectKey(objectKey.toByteArray()); + return mBdb->removeOne(baObjectKey); +} + +QString JsonDbObjectTable::errorMessage() const +{ + return mBdb->errorMessage(); +} + +GetObjectsResult JsonDbObjectTable::getObjects(const QString &keyName, const QJsonValue &keyValue, const QString &objectType) +{ + GetObjectsResult result; + JsonDbObjectList objectList; + bool typeSpecified = !objectType.isEmpty(); + + if (keyName == JsonDbString::kUuidStr) { + ObjectKey objectKey(keyValue.toString()); + JsonDbObjectList objectList; + JsonDbObject object; + bool ok = get(objectKey, &object); + if (ok + && (!typeSpecified + || (object.value(JsonDbString::kTypeStr).toString() == objectType))) + objectList.append(object); + result.data = objectList; + return result; + } + + if (!mIndexes.contains(keyName) && (keyName != JsonDbString::kTypeStr)) { + qDebug() << "ObjectTable::getObject" << "no index for" << keyName << mFilename; + return result; + } + + if (!mIndexes.contains(keyName) && (keyName == JsonDbString::kTypeStr)) { + bool isInTransaction = mBdb->writeTransaction(); + JsonDbBtree::Transaction *txn = mBdb->writeTransaction() ? mBdb->writeTransaction() : mBdb->beginWrite(); + JsonDbBtree::Cursor cursor(txn); + for (bool ok = cursor.first(); ok; ok = cursor.next()) { + QByteArray baKey, baObject; + ok = cursor.current(&baKey, &baObject); + if (!ok) + break; + if (baKey.size() != 16) // state key is 5 bytes, or history key is 5 + 16 bytes + continue; + JsonDbObject object = QJsonDocument::fromBinaryData(baObject).object(); + if (object.value(JsonDbString::kDeletedStr).toBool()) + continue; + objectList.append(object); + } + if (!isInTransaction) + txn->abort(); + result.data = objectList; + return result; + } + + const IndexSpec *indexSpec = &mIndexes[keyName]; + QByteArray forwardKey(makeForwardKey(makeFieldValue(keyValue, indexSpec->propertyType), ObjectKey())); + //fprintf(stderr, "getObject bdb=%p\n", indexSpec->index->bdb()); + if (indexSpec->lazy) + updateIndex(indexSpec->index); + bool isInTransaction = indexSpec->index->bdb()->writeTransaction(); + JsonDbBtree::Transaction *txn = indexSpec->index->bdb()->writeTransaction() ? indexSpec->index->bdb()->writeTransaction() : indexSpec->index->bdb()->beginWrite(); + JsonDbBtree::Cursor cursor(txn); + if (cursor.seekRange(forwardKey)) { + do { + QByteArray checkKey; + QByteArray forwardValue; + bool ok = cursor.current(&checkKey, &forwardValue); + QJsonValue checkValue; + forwardKeySplit(checkKey, checkValue); + if (checkValue != keyValue) + break; + + ObjectKey objectKey; + forwardValueSplit(forwardValue, objectKey); + if (jsondbSettings->debug() && jsondbSettings->verbose()) + qDebug() << "ok" << ok << "forwardValue" << forwardValue << "objectKey" << objectKey; + + JsonDbObject map; + if (get(objectKey, &map)) { + //qDebug() << "ObjectTable::getObject" << "deleted" << map.value(JsonDbString::kDeletedStr, false).toBool(); + if (map.contains(JsonDbString::kDeletedStr) && map.value(JsonDbString::kDeletedStr).toBool()) + continue; + if (typeSpecified && (map.value(JsonDbString::kTypeStr).toString() != objectType)) + continue; + + objectList.append(map); + } else { + if (jsondbSettings->debug()) + qDebug() << "Failed to get object" << objectKey << errorMessage(); + } + } while (cursor.next()); + } + if (!isInTransaction) + txn->abort(); + + result.data = objectList; + return result; +} + +quint32 JsonDbObjectTable::storeStateChange(const ObjectKey &key, const JsonDbUpdate &change) +{ + quint32 stateNumber = mStateNumber + 1; + + int oldSize = mStateChanges.size(); + mStateChanges.resize(oldSize + 20); + uchar *data = (uchar *)mStateChanges.data() + oldSize; + + qToBigEndian(key, data); + qToBigEndian<quint32>(change.action, data+16); + if (!change.oldObject.isEmpty()) + mStateObjectChanges.append(change.oldObject); + return stateNumber; +} + +quint32 JsonDbObjectTable::changesSince(quint32 stateNumber, QMap<ObjectKey,JsonDbUpdate> *changes) +{ + if (!changes) + return -1; + stateNumber = qMax(quint32(1), stateNumber+1); + + QElapsedTimer timer; + if (jsondbSettings->performanceLog()) + timer.start(); + bool inTransaction = mBdb->writeTransaction(); + JsonDbBtree::Transaction *txn = mBdb->writeTransaction() ? mBdb->writeTransaction() : mBdb->beginWrite(); + JsonDbBtree::Cursor cursor(txn); + QByteArray baStateKey(5, 0); + makeStateKey(baStateKey, stateNumber); + + QMap<ObjectKey,JsonDbUpdate> changeMap; // collect one change per uuid + + if (cursor.seekRange(baStateKey)) { + do { + QByteArray baObject; + bool ok = cursor.current(&baStateKey, &baObject); + if (!ok) + break; + + if (!isStateKey(baStateKey)) + continue; + stateNumber = qFromBigEndian<quint32>((const uchar *)baStateKey.constData()); + + if (baObject.size() % 20 != 0) { + qWarning() << __FUNCTION__ << __LINE__ << "state size must be a multiplier 20" + << baObject.size() << baObject.toHex(); + continue; + } + + for (int i = 0; i < baObject.size() / 20; ++i) { + const uchar *data = (const uchar *)baObject.constData() + i*20; + ObjectKey objectKey = qFromBigEndian<ObjectKey>(data); + QByteArray baObjectKey(objectKey.key.toRfc4122()); + quint32 action = qFromBigEndian<quint32>(data + 16); + QByteArray baValue; + QJsonObject oldObject; + if ((action != JsonDbNotification::Create) + && mBdb->getOne(baStateKey + baObjectKey, &baValue)) { + oldObject = QJsonDocument::fromBinaryData(baValue).object(); + Q_ASSERT(objectKey == ObjectKey(oldObject.value(JsonDbString::kUuidStr).toString())); + } + QJsonObject newObject; + mBdb->getOne(baObjectKey, &baValue); + newObject = QJsonDocument::fromBinaryData(baValue).object(); + if (jsondbSettings->debug()) + qDebug() << "change" << action << endl << oldObject << endl << newObject; + + JsonDbUpdate change(oldObject, newObject, JsonDbNotification::Action(action)); + if (changeMap.contains(objectKey)) { + JsonDbUpdate oldChange = changeMap.value(objectKey); + // create followed by delete cancels out + JsonDbNotification::Action newAction = JsonDbNotification::Action(action); + JsonDbNotification::Action oldAction = oldChange.action; + if ((oldAction == JsonDbNotification::Create) + && (newAction == JsonDbNotification::Delete)) { + changeMap.remove(objectKey); + } else { + if ((oldAction == JsonDbNotification::Delete) + && (newAction == JsonDbNotification::Create)) + oldChange.action = JsonDbNotification::Update; + changeMap.insert(objectKey, oldChange); + } + } else { + changeMap.insert(objectKey, change); + } + } + } while (cursor.next()); + } + *changes = changeMap; + if (!inTransaction) + txn->abort(); + + if (jsondbSettings->performanceLog()) + qDebug() << "changesSince" << mFilename << timer.elapsed() << "ms"; + return mStateNumber; +} + +quint32 JsonDbObjectTable::changesSince(quint32 stateNumber, const QSet<QString> &limitTypes, QList<JsonDbUpdate> *updateList, JsonDbObjectTable::TypeChangeMode splitTypeChanges) +{ + if (jsondbSettings->verbose()) + qDebug() << "changesSince" << stateNumber << limitTypes << "{"; + QMap<ObjectKey,JsonDbUpdate> changeSet; + changesSince(stateNumber, &changeSet); + QList<JsonDbUpdate> changeList; + bool allTypes = limitTypes.isEmpty(); + foreach (const JsonDbUpdate &update, changeSet) { + const JsonDbObject &oldObject = update.oldObject; + const JsonDbObject &newObject = update.newObject; + QString oldType = oldObject.value(JsonDbString::kTypeStr).toString(); + QString newType = newObject.value(JsonDbString::kTypeStr).toString(); + // if the types don't match, split into two updates + if (!oldObject.isEmpty() && oldType != newType && splitTypeChanges == JsonDbObjectTable::SplitTypeChanges) { + if (allTypes || limitTypes.contains(oldType)) { + JsonDbObject tombstone(oldObject); + tombstone.insert(QLatin1String("_deleted"), true); + changeList.append(JsonDbUpdate(oldObject, tombstone, JsonDbNotification::Delete)); + } + if (allTypes || limitTypes.contains(newType)) { + changeList.append(JsonDbUpdate(JsonDbObject(), newObject, JsonDbNotification::Create)); + } + } else if (allTypes || limitTypes.contains(oldType) || limitTypes.contains(newType)) { + changeList.append(update); + } + } + if (updateList) + *updateList = changeList; + if (jsondbSettings->verbose()) + qDebug() << "changesSince" << changeList.size() << "changes" << "}"; + return mStateNumber; +} + +#include "moc_jsondbobjecttable.cpp" + +QT_END_NAMESPACE_JSONDB_PARTITION diff --git a/src/partition/jsondbobjecttable.h b/src/partition/jsondbobjecttable.h new file mode 100644 index 00000000..a98b6b47 --- /dev/null +++ b/src/partition/jsondbobjecttable.h @@ -0,0 +1,178 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef JSONDB_OBJECT_TABLE_H +#define JSONDB_OBJECT_TABLE_H + +#include <QObject> +#include <QHash> +#include <QList> +#include <QPair> +#include <QtEndian> + +#include "jsondbobjectkey.h" +#include "jsondbbtree.h" + +#include <qjsonarray.h> +#include <qjsonobject.h> +#include <qjsonvalue.h> + +#include <jsondbobject.h> +#include "jsondbpartition.h" +#include "jsondbindex.h" + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE_JSONDB_PARTITION + +class JsonDbBtree; + +inline QDebug &operator<<(QDebug &qdb, const JsonDbUpdate &oc) +{ + qdb.nospace() << "JsonDbUpdate("; + //qdb.nospace() << oc.objectKey; + qdb.nospace() << "action = "; + switch (oc.action) { + case JsonDbNotification::None: qdb.nospace() << "None"; break; + case JsonDbNotification::Create: qdb.nospace() << "Created"; break; + case JsonDbNotification::Update: qdb.nospace() << "Updated"; break; + case JsonDbNotification::Delete: qdb.nospace() << "Deleted"; break; + } + if (oc.action != JsonDbNotification::Create) + qdb.nospace() << ", oldObject = " << oc.oldObject; + qdb.nospace() << ")"; + return qdb; +} + + +class Q_JSONDB_PARTITION_EXPORT JsonDbObjectTable : public QObject +{ + Q_OBJECT +public: + enum SyncFlag { + SyncObjectTable = 0x1, + SyncIndexes = 0x2 + }; + Q_DECLARE_FLAGS(SyncFlags, SyncFlag) + + JsonDbObjectTable(JsonDbPartition *parent=0); + ~JsonDbObjectTable(); + + QString filename() const { return mFilename; } + bool open(const QString &filename); + void close(); + JsonDbPartition *partition() const { return mPartition; } + JsonDbBtree *bdb() const { return mBdb; } + bool begin(); + void begin(JsonDbIndex *btree); + bool commit(quint32); + bool abort(); + bool compact(); + void sync(SyncFlags flags); + + JsonDbStat stat() const; + void flushCaches(); + + quint32 stateNumber() const { return mStateNumber; } + quint32 storeStateChange(const ObjectKey &key, const JsonDbUpdate &stateChange); + enum TypeChangeMode { + SplitTypeChanges, KeepTypeChanges + }; + quint32 changesSince(quint32 stateNumber, const QSet<QString> &limitTypes, QList<JsonDbUpdate> *updateList, TypeChangeMode mode=KeepTypeChanges); + + IndexSpec *indexSpec(const QString &indexName); + QHash<QString, IndexSpec> indexSpecs() const; + + bool addIndex(const QString &indexName, + const QString &propertyName = QString(), + const QString &propertyType = QString("string"), + const QStringList &objectTypes = QStringList(), + const QString &propertyFunction = QString(), + const QString &locale = QString(), + const QString &collation = QString(), + const QString &casePreference = QString(), + Qt::CaseSensitivity caseSensitivity = Qt::CaseSensitive); + bool addIndexOnProperty(const QString &propertyName, + const QString &propertyType = QString("string"), + const QString &objectType = QString()) + { 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); + void deindexObject(const ObjectKey &objectKey, JsonDbObject object, quint32 stateNumber); + void updateIndex(JsonDbIndex *index); + + bool get(const ObjectKey &objectKey, QJsonObject *object, bool includeDeleted=false); + bool put(const ObjectKey &objectKey, const JsonDbObject &object); + bool remove(const ObjectKey &objectKey); + + QString errorMessage() const; + + GetObjectsResult getObjects(const QString &keyName, const QJsonValue &keyValue, const QString &objectType); + +private: + quint32 changesSince(quint32 stateNumber, QMap<ObjectKey,JsonDbUpdate> *changes); + +private: + JsonDbPartition *mPartition; + QString mFilename; + JsonDbBtree *mBdb; + QHash<QString,IndexSpec> mIndexes; // indexed by full path, e.g., _type or _name.first + QVector<JsonDbBtree::Transaction *> mBdbTransactions; + + quint32 mStateNumber; + + // intermediate state changes until the commit is called + QByteArray mStateChanges; + QList<JsonDbObject> mStateObjectChanges; +}; + +void makeStateKey(QByteArray &baStateKey, quint32 stateNumber); +bool isStateKey(const QByteArray &baStateKey); + +Q_DECLARE_OPERATORS_FOR_FLAGS(JsonDbObjectTable::SyncFlags) + +QT_END_NAMESPACE_JSONDB_PARTITION + +QT_END_HEADER + +#endif // JSONDB_OBJECT_TABLE_H diff --git a/src/partition/jsondbobjecttypes_impl_p.h b/src/partition/jsondbobjecttypes_impl_p.h new file mode 100644 index 00000000..f2833c9b --- /dev/null +++ b/src/partition/jsondbobjecttypes_impl_p.h @@ -0,0 +1,352 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef JSONDB_OBJECTTYPES_IMPL_P_H +#define JSONDB_OBJECTTYPES_IMPL_P_H + +#include "jsondbpartitionglobal.h" +#include "jsondbobjecttypes_p.h" +#include "jsondbschemamanager_p.h" + +QT_BEGIN_NAMESPACE_JSONDB_PARTITION + +inline QJsonObjectTypes::ValueList::ValueList(const QJsonArray &list) : QJsonArray(list) +{} + +inline uint QJsonObjectTypes::ValueList::size() const +{ + return QJsonArray::size(); +} + +inline QJsonObjectTypes::ValueList::const_iterator QJsonObjectTypes::ValueList::constBegin() const +{ + return const_iterator(0, this); +} + +inline QJsonObjectTypes::ValueList::const_iterator QJsonObjectTypes::ValueList::constEnd() const +{ + return const_iterator(size(), this); +} + +inline QJsonObjectTypes::ValueList::const_iterator::const_iterator() + : m_index(-1) + , m_list(0) +{} + +inline QJsonObjectTypes::Value QJsonObjectTypes::ValueList::const_iterator::operator *() const +{ + Q_ASSERT(isValid()); + return Value(m_index, *m_list); +} + +inline bool QJsonObjectTypes::ValueList::const_iterator::operator !=(const const_iterator &other) const +{ + return m_index != other.m_index || m_list != other.m_list; +} + +inline QJsonObjectTypes::ValueList::const_iterator& QJsonObjectTypes::ValueList::const_iterator::operator ++() +{ + m_index++; + return *this; +} + +inline QJsonObjectTypes::ValueList::const_iterator::const_iterator(int begin, const QJsonArray *list) + : m_index(begin) + , m_list(list) +{} + +inline bool QJsonObjectTypes::ValueList::const_iterator::isValid() const +{ + return m_index != -1 && m_index < m_list->size(); +} + +inline QJsonObjectTypes::Value::Value(Key propertyName, const QJsonObject &map) + : m_index(-1) + , m_property(propertyName) + , m_value(map) + , m_type(propertyName.isEmpty() ? RootMap : Map) +{} + +inline QJsonObjectTypes::Value::Value(const int index, const QJsonArray &list) + : m_index(index) + , m_value(list) + , m_type(List) +{} + +inline int QJsonObjectTypes::Value::toInt(bool *ok) const +{ + if (m_intCache.isValid()) { + *ok = true; + return m_intCache.value(); + } + int result = 0; + QJsonValue::Type type; + switch (m_type) { + case Map: + type = typeMap(); + if (type == QJsonValue::Double) { + QJsonValue v = map().value(m_property); + double doubleResult = v.toDouble(); + int intResult = (int)doubleResult; + if ((double)intResult == doubleResult) { + *ok = true; + result = intResult; + } else { + *ok = false; + } + } else { + *ok = false; + } + m_intCache.set(*ok, result); + return result; + case List: + type = typeList(); + if (type == QJsonValue::Double) { + QJsonValue v = list().at(m_index); + double doubleResult = v.toDouble(); + int intResult = (int)doubleResult; + if ((double)intResult == doubleResult) { + *ok = true; + result = intResult; + } else { + *ok = false; + } + } else { + *ok = false; + } + m_intCache.set(*ok, result); + return result; + case RootMap: + break; + default: + Q_ASSERT(false); + } + *ok = false; + return -1; +} + +inline double QJsonObjectTypes::Value::toDouble(bool *ok) const +{ + if (m_doubleCache.isValid()) { + *ok = true; + return m_doubleCache.value(); + } + double result; + QJsonValue::Type type; + switch (m_type) { + case Map: + type = typeMap(); + *ok = type == QJsonValue::Double; + result = map().value(m_property).toDouble(); + m_doubleCache.set(*ok, result); + return result; + case List: + type = typeList(); + *ok = type == QJsonValue::Double; + result = list().at(m_index).toDouble(); + m_doubleCache.set(*ok, result); + return result; + case RootMap: + break; + default: + Q_ASSERT(false); + } + *ok = false; + return -1; +} + +inline QJsonObjectTypes::ValueList QJsonObjectTypes::Value::toList(bool *ok) const +{ + *ok = true; + switch (m_type) { + case Map: + *ok = typeMap() == QJsonValue::Array; + return map().value(m_property).toArray(); + case List: + *ok = typeList() == QJsonValue::Array; + return list().at(m_index).toArray(); + case RootMap: + return m_value.toArray(); + default: + Q_ASSERT(false); + } + *ok = false; + return ValueList(QJsonArray()); +} + +inline QString QJsonObjectTypes::Value::toString(bool *ok) const +{ + switch (m_type) { + case Map: + *ok = typeMap() == QJsonValue::String; + return map().value(m_property).toString(); + case List: + *ok = typeList() == QJsonValue::String; + return list().at(m_index).toString(); + case RootMap: + *ok = true; + return QString(); // useful for debugging + default: + Q_ASSERT(false); + } + *ok = false; + return QString(); +} + +inline bool QJsonObjectTypes::Value::toBool(bool *ok) const +{ + switch (m_type) { + case Map: + *ok = typeMap() == QJsonValue::Bool; + return map().value(m_property).toBool(); + case List: + *ok = typeList() == QJsonValue::Bool; + return list().at(m_index).toBool(); + case RootMap: + default: + Q_ASSERT(false); + } + *ok = false; + return false; +} + +inline void QJsonObjectTypes::Value::toNull(bool *ok) const +{ + switch (m_type) { + case Map: + *ok = typeMap() == QJsonValue::Null; + case List: + *ok = typeList() == QJsonValue::Null; + case RootMap: + default: + Q_ASSERT(false); + } + *ok = false; +} + +inline QJsonObjectTypes::Object QJsonObjectTypes::Value::toObject(bool *ok) const +{ + switch (m_type) { + case Map: + *ok = typeMap() == QJsonValue::Object; + return map().value(m_property).toObject(); + case List: + *ok = typeList() == QJsonValue::Object; + return list().at(m_index).toObject(); + case RootMap: + *ok = true; + return static_cast<Object>(m_value.toObject()); + default: + Q_ASSERT(false); + } + *ok = false; + return Object(QJsonObject()); +} + +inline const QJsonObject QJsonObjectTypes::Value::map() const +{ + Q_ASSERT(m_type == Map); + Q_ASSERT(!m_property.isEmpty()); + return m_value.toObject(); +} + +inline const QJsonArray QJsonObjectTypes::Value::list() const +{ + Q_ASSERT(m_type == List); + Q_ASSERT(m_index >= 0); + return m_value.toArray(); +} + +inline QJsonValue::Type QJsonObjectTypes::Value::typeMap() const +{ + if (m_qsonTypeCache.isValid()) + return m_qsonTypeCache.value(); + QJsonValue::Type result = map().value(m_property).type(); + m_qsonTypeCache.set(true, result); + return result; +} + +inline QJsonValue::Type QJsonObjectTypes::Value::typeList() const +{ + if (m_qsonTypeCache.isValid()) + return m_qsonTypeCache.value(); + QJsonValue::Type result = list().at(m_index).type(); + m_qsonTypeCache.set(true, result); + return result; +} + +inline QJsonObjectTypes::Object::Object() +{} + +inline QJsonObjectTypes::Object::Object(const QJsonObject &map) + : QJsonObject(map) +{} + +inline QJsonObjectTypes::Value QJsonObjectTypes::Object::property(const QJsonObjectTypes::Key& name) const +{ + return Value(name, *this); +} + +inline QList<QJsonObjectTypes::Key> QJsonObjectTypes::Object::propertyNames() const { return QJsonObject::keys(); } + +inline QJsonObjectTypes::Service::Service(JsonDbSchemaManager *schemas) + : m_schemas(schemas) +{} + +inline QJsonObject QJsonObjectTypes::Service::error() const +{ + return m_errorMap; +} + +inline void QJsonObjectTypes::Service::setError(const QString &message) +{ + m_errorMap.insert(JsonDbString::kCodeStr, JsonDbError::FailedSchemaValidation); + m_errorMap.insert(JsonDbString::kMessageStr, message); +} + +inline SchemaValidation::Schema<QJsonObjectTypes> QJsonObjectTypes::Service::loadSchema(const QString &schemaName) +{ + return m_schemas->schema(schemaName, this); +} + +QT_END_NAMESPACE_JSONDB_PARTITION + +#endif // JSONDB_OBJECTTYPES_IMPL_P_H diff --git a/src/partition/jsondbobjecttypes_p.h b/src/partition/jsondbobjecttypes_p.h new file mode 100644 index 00000000..f2b42f58 --- /dev/null +++ b/src/partition/jsondbobjecttypes_p.h @@ -0,0 +1,180 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef JSONDB_OBJECTTYPES_P_H +#define JSONDB_OBJECTTYPES_P_H + +#include "jsondbpartitionglobal.h" +#include "jsondbstrings.h" + +#include "schema-validation/object.h" + +#include <QPair> + +#include <qjsonarray.h> +#include <qjsonobject.h> +#include <qjsonvalue.h> + +QT_BEGIN_NAMESPACE_JSONDB_PARTITION + +class JsonDbSchemaManager; + +/** + \internal + This is type definition for schema validation framework. It was + created because of planed change of data representation in jsondb + (Bson -> Qson -> QtJson). Essentially schema validation is + independent from data representation. The performance cost of this + indirection is about 0. We can consider removing it in future or + leave it and check different data representation (think about + QJSValue). + + These types define the simplest types in JSON. + */ +class QJsonObjectTypes { +public: + typedef QString Key; + + class Value; + class ValueList : protected QJsonArray + { + public: + inline ValueList(const QJsonArray &list); + + // interface + class const_iterator; + inline uint size() const; + inline const_iterator constBegin() const; + inline const_iterator constEnd() const; + class const_iterator + { + friend class ValueList; + public: + inline const_iterator(); + inline Value operator *() const; + inline bool operator !=(const const_iterator &other) const; + inline const_iterator& operator ++(); + + private: + inline const_iterator(int begin, const QJsonArray *list); + inline bool isValid() const; + + int m_index; + const QJsonArray *m_list; + }; + }; + + class Object; + class Value + { + enum Type {List, Map, RootMap}; + + template<class T> + class Cache : protected QPair<bool, T> + { + public: + Cache() + : QPair<bool, T>(false, T()) + {} + bool isValid() const { return QPair<bool, T>::first; } + T value() const { Q_ASSERT(isValid()); return QPair<bool, T>::second; } + void set(const bool ok, const T &value) { QPair<bool, T>::first = ok; QPair<bool, T>::second = value; } + }; + + public: + inline Value(Key propertyName, const QJsonObject &map); + inline Value(const int index, const QJsonArray &list); + + // interface + inline int toInt(bool *ok) const; + inline double toDouble(bool *ok) const; + inline ValueList toList(bool *ok) const; + inline QString toString(bool *ok) const; + inline bool toBool(bool *ok) const; + inline void toNull(bool *ok) const; + inline Object toObject(bool *ok) const; + + private: + inline const QJsonObject map() const; + inline const QJsonArray list() const; + inline QJsonValue::Type typeMap() const; + inline QJsonValue::Type typeList() const; + + const int m_index; + const Key m_property; + + const QJsonValue m_value; + const Type m_type; + + mutable Cache<QJsonValue::Type> m_qsonTypeCache; + mutable Cache<int> m_intCache; + mutable Cache<double> m_doubleCache; + }; + + class Object : public QJsonObject + { + public: + inline Object(const QJsonObject &map); + + // interface + inline Object(); + inline Value property(const Key& name) const; + inline QList<Key> propertyNames() const; + }; + + class Service { + public: + inline Service(JsonDbSchemaManager *schemas); + inline QJsonObject error() const; + + // interface + inline void setError(const QString &message); + inline SchemaValidation::Schema<QJsonObjectTypes> loadSchema(const QString &schemaName); + + private: + JsonDbSchemaManager *m_schemas; + QJsonObject m_errorMap; + }; +}; + +QT_END_NAMESPACE_JSONDB_PARTITION + +#endif // JSONDB_OBJECTTYPES_P_H diff --git a/src/partition/jsondbowner.cpp b/src/partition/jsondbowner.cpp new file mode 100644 index 00000000..0d07fe3a --- /dev/null +++ b/src/partition/jsondbowner.cpp @@ -0,0 +1,292 @@ +/**************************************************************************** +** +** 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 "jsondbowner.h" +#include "jsondbpartition.h" +#include "jsondbsettings.h" +#include "jsondbstrings.h" +#include <qdebug.h> + +#ifdef Q_OS_UNIX +#include <pwd.h> +#include <grp.h> +#include <errno.h> +#endif + +QT_BEGIN_NAMESPACE_JSONDB_PARTITION + +JsonDbOwner::JsonDbOwner( QObject *parent ) + : QObject(parent), mStorageQuota(-1), mAllowAll(false) +{ +} + +void cleanQueryList(QList<JsonDbQuery *> &queryList) +{ + foreach (JsonDbQuery *q, queryList) + delete q; + queryList.clear(); +} + +JsonDbOwner::~JsonDbOwner() +{ + QMap<QString, QList<JsonDbQuery *> > list; + foreach (list, mAllowedObjectQueries) { + foreach (QList<JsonDbQuery *> queryList, list) + cleanQueryList(queryList); + } +} + +void JsonDbOwner::setAllowedObjects(const QString &partition, const QString &op, + const QList<QString> &queries) +{ + mAllowedObjects[partition][op] = queries; + cleanQueryList(mAllowedObjectQueries[partition][op]); + QJsonObject bindings; + bindings.insert(QLatin1String("domain"), domain()); + bindings.insert(QLatin1String("owner"), ownerId()); + foreach (const QString &query, queries) { + mAllowedObjectQueries[partition][op].append(JsonDbQuery::parse(query, bindings)); + } +} + +void JsonDbOwner::setCapabilities(QJsonObject &applicationCapabilities, JsonDbPartition *partition) +{ + QJsonObject request; + GetObjectsResult result = partition->getObjects(JsonDbString::kTypeStr, QString("Capability")); + JsonDbObjectList translations = result.data; + //qDebug() << "JsonDbOwner::setCapabilities" << "translations" << translations; + + QMap<QString, QSet<QString> > allowedObjects; + const QStringList ops = (QStringList() << "read" << "write" << "setOwner"); + + for (int i = 0; i < translations.size(); ++i) { + JsonDbObject translation = translations.at(i); + QString name = translation.value("name").toString(); + QString partition = translation.value("partition").toString(); + partition = partition.replace("%owner", ownerId()); + if (applicationCapabilities.contains(name)) { + QJsonObject accessRules = translation.value("accessRules").toObject(); + QVariantList accessTypesAllowed = + applicationCapabilities.value(name).toArray().toVariantList(); + foreach (QVariant accessTypeAllowed, accessTypesAllowed) { + QJsonObject accessTypeTranslation = + accessRules.value(accessTypeAllowed.toString()).toObject(); + foreach (const QString op, ops) { + if (accessTypeTranslation.contains(op)) { + QStringList rules; + QJsonArray jsonRules = accessTypeTranslation.value(op).toArray(); + for (int r = 0; r < jsonRules.size(); r++) { + rules.append(jsonRules[r].toString()); + } + allowedObjects[op].unite(QSet<QString>::fromList(rules)); + } + } + } + } + foreach (const QString op, ops) { + if (!allowedObjects.value(op).empty()) + setAllowedObjects(partition, op, allowedObjects.value(op).toList()); + } + allowedObjects.clear(); + } + if (jsondbSettings->verbose()) { + qDebug() << "setCapabilities" << mOwnerId; + qDebug() << mAllowedObjects << endl; + } +} + +bool JsonDbOwner::isAllowed(JsonDbObject &object, const QString &partition, + const QString &op) const +{ + if (mAllowAll || !jsondbSettings->enforceAccessControl()) + return true; + + QString _type = object[QLatin1String("_type")].toString(); + QStringList domainParts = _type.split(QLatin1Char('.'), QString::SkipEmptyParts); + QString typeDomain; + // TODO: handle reverse domains like co.uk.foo, co.au.bar, co.nz.foo , .... correctly + if (domainParts.count() > 2) + typeDomain = _type.left(_type.lastIndexOf(QLatin1Char('.'))+1); + else + // Capability queries with _typeDomain should fail, if there is not long enough domain + typeDomain = QLatin1String("public.domain.fail."); + QJsonValue tdval = QJsonValue(typeDomain); + + QList<JsonDbQuery *> queries = mAllowedObjectQueries[partition][op]; + foreach (JsonDbQuery *query, queries) { + query->bind(QString(QLatin1String("typeDomain")), tdval); + if (query->match(object, NULL, NULL)) + return true; + } + queries = mAllowedObjectQueries[QLatin1String("all")][op]; + foreach (JsonDbQuery *query, queries) { + query->bind(QString(QLatin1String("typeDomain")), tdval); + if (query->match(object, NULL, NULL)) + return true; + } + if (jsondbSettings->verbose()) { + qDebug () << "Not allowed" << ownerId() << partition << _type << op; + } + return false; +} + +bool JsonDbOwner::_setOwnerCapabilities(struct passwd *pwd, JsonDbPartition *partition) +{ +#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC) + mOwnerId = QString::fromLocal8Bit(pwd->pw_name); + + // Parse domain from username + // TODO: handle reverse domains like co.uk.foo, co.au.bar, co.nz.foo , .... correctly + QStringList domainParts = mOwnerId.split(QLatin1Char('.'), QString::SkipEmptyParts); + if (domainParts.count() > 2) + mDomain = domainParts.at(0)+QLatin1Char('.')+domainParts.at(1); + else + mDomain = QStringLiteral("public"); + + if (jsondbSettings->debug()) + qDebug() << "username" << mOwnerId << "uid" << pwd->pw_uid << "domain set to" << mDomain; + + // Get capabilities from supplementary groups + if (pwd->pw_uid) { + int ngroups = 128; + gid_t groups[128]; + bool setOwner = false; + QJsonObject capabilities; + if (::getgrouplist(pwd->pw_name, pwd->pw_gid, groups, &ngroups) != -1) { + struct group *gr; + for (int i = 0; i < ngroups; i++) { + gr = ::getgrgid(groups[i]); + if (gr && ::strcasecmp (gr->gr_name, "identity") == 0) + setOwner = true; + } + // Start from 1 to omit the primary group + for (int i = 1; i < ngroups; i++) { + gr = ::getgrgid(groups[i]); + QJsonArray value; + if (!gr || ::strcasecmp (gr->gr_name, "identity") == 0) + continue; + if (setOwner) + value.append(QJsonValue(QLatin1String("setOwner"))); + value.append(QJsonValue(QLatin1String("rw"))); + capabilities.insert(QString::fromLocal8Bit(gr->gr_name), value); + if (jsondbSettings->debug()) + qDebug() << "Adding capability" << QString::fromLocal8Bit(gr->gr_name) + << "to user" << mOwnerId << "setOwner =" << setOwner; + } + if (ngroups) + setCapabilities(capabilities, partition); + } else { + qWarning() << Q_FUNC_INFO << mOwnerId << "belongs to too many groups (>128)"; + } + } else { + // root can access all + setAllowAll(true); + setStorageQuota(-1); + } + + // Read quota from security object + GetObjectsResult result = partition->getObjects(JsonDbString::kTypeStr, QString("Quota")); + JsonDbObjectList securityObjects; + for (int i = 0; i < result.data.size(); i++) { + JsonDbObject doc = result.data.at(i); + if (doc.value(JsonDbString::kTokenStr).toString() == mOwnerId) + securityObjects.append(doc); + } + if (securityObjects.size() == 1) { + QJsonObject securityObject = securityObjects.at(0); + QJsonObject capabilities = securityObject.value("capabilities").toObject(); + QStringList keys = capabilities.keys(); + if (keys.contains("quotas")) { + QJsonObject quotas = capabilities.value("quotas").toObject(); + int storageQuota = quotas.value("storage").toDouble(); + setStorageQuota(storageQuota); + } + } else if (!securityObjects.isEmpty()) { + qWarning() << Q_FUNC_INFO << "Wrong number of security objects found." << securityObjects.size(); + return false; + } +#endif + return true; +} + +bool JsonDbOwner::setOwnerCapabilities(QString username, JsonDbPartition *partition) +{ + if (!jsondbSettings->enforceAccessControl()) { + setAllowAll(true); + return true; + } +#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC) + struct passwd *pwd = ::getpwnam(username.toLocal8Bit()); + if (!pwd) { + qWarning() << Q_FUNC_INFO << "pwd entry for" << username << + "not found" << strerror(errno); + return false; + } + return _setOwnerCapabilities (pwd, partition); +#else + setAllowAll(true); + return true; +#endif +} + +bool JsonDbOwner::setOwnerCapabilities(uid_t uid, JsonDbPartition *partition) +{ + if (!jsondbSettings->enforceAccessControl()) { + setAllowAll(true); + return true; + } +#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC) + struct passwd *pwd = ::getpwuid(uid); + if (!pwd) { + qWarning() << Q_FUNC_INFO << "pwd entry for" << uid << + "not found" << strerror(errno); + return false; + } + return _setOwnerCapabilities (pwd, partition); +#else + setAllowAll(true); + return true; +#endif +} + +#include "moc_jsondbowner.cpp" + +QT_END_NAMESPACE_JSONDB_PARTITION diff --git a/src/partition/jsondbowner.h b/src/partition/jsondbowner.h new file mode 100644 index 00000000..c3416103 --- /dev/null +++ b/src/partition/jsondbowner.h @@ -0,0 +1,101 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef JSONDB_OWNER_H +#define JSONDB_OWNER_H + +#include <QObject> +#include <QMap> +#include <QString> +#include <QSet> + +#include "jsondbpartitionglobal.h" +#include "jsondbquery.h" +#include "jsondbobject.h" + +QT_BEGIN_HEADER + +class TestPartition; +struct passwd; + +QT_BEGIN_NAMESPACE_JSONDB_PARTITION + +class Q_JSONDB_PARTITION_EXPORT JsonDbOwner : public QObject +{ + Q_OBJECT +public: + JsonDbOwner( QObject *parent = 0 ); + ~JsonDbOwner(); + QString ownerId() const { return mOwnerId; } + void setOwnerId(const QString &ownerId) { mOwnerId = ownerId; } + QString domain() const { return mDomain; } + void setDomain(const QString &domain) { mDomain = domain; } + + QList<QString> allowedObjects(const QString &op) const; + + void setAllowedObjects(const QString &partition, const QString &op, + const QList<QString> &queries); + void setCapabilities(QJsonObject &capabilities, JsonDbPartition *partition); + bool allowAll() const { return mAllowAll; } + void setAllowAll(bool allowAll) { mAllowAll = allowAll; } + + bool isAllowed(JsonDbObject &object, const QString &partition, const QString &op) const; + int storageQuota() const { return mStorageQuota; } + void setStorageQuota(int storageQuota) { mStorageQuota = storageQuota; } + bool setOwnerCapabilities(uid_t uid, JsonDbPartition *partition); + bool setOwnerCapabilities(QString username, JsonDbPartition *partition); + +private: + QString mOwnerId; // from security object + QString mDomain; // from security object + QMap<QString, QMap<QString, QList<QString> > > mAllowedObjects; // list of querie strings per partition + QMap<QString, QMap<QString, QList<JsonDbQuery *> > > mAllowedObjectQueries; + int mStorageQuota; + bool mAllowAll; + friend class ::TestPartition; + bool _setOwnerCapabilities(struct passwd *pwd, JsonDbPartition *partition); +}; + +QT_END_NAMESPACE_JSONDB_PARTITION + +QT_END_HEADER + +#endif // JSONDB_OWNER_H diff --git a/src/partition/jsondbpartition.cpp b/src/partition/jsondbpartition.cpp new file mode 100644 index 00000000..80500d42 --- /dev/null +++ b/src/partition/jsondbpartition.cpp @@ -0,0 +1,1920 @@ +/**************************************************************************** +** +** 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 <QObject> +#include <QDebug> +#include <QDir> +#include <QFile> +#include <QFileInfo> +#include <QJsonDocument> +#include <QRegExp> +#include <QString> +#include <QElapsedTimer> +#include <QUuid> +#include <QtAlgorithms> +#include <QtEndian> +#include <QStringBuilder> +#include <QTimerEvent> +#include <QMap> + +#include <fcntl.h> +#include <unistd.h> +#include <stdlib.h> + +#include "jsondbstrings.h" +#include "jsondberrors.h" +#include "jsondbpartition.h" +#include "jsondbindex.h" +#include "jsondbindexquery.h" +#include "jsondbobjecttable.h" +#include "jsondbbtree.h" +#include "jsondbsettings.h" +#include "jsondbview.h" +#include "jsondbschemamanager_impl_p.h" +#include "jsondbobjecttypes_impl_p.h" + +QT_BEGIN_NAMESPACE_JSONDB_PARTITION + +const QString gDatabaseSchemaVersion = "0.2"; + +JsonDbPartition::JsonDbPartition(const QString &filename, const QString &name, JsonDbOwner *owner, QObject *parent) + : QObject(parent) + , mObjectTable(0) + , mPartitionName(name) + , mFilename(filename) + , mTransactionDepth(0) + , mWildCardPrefixRegExp("([^*?\\[\\]\\\\]+).*") + , mMainSyncTimerId(-1) + , mIndexSyncTimerId(-1) + , mDefaultOwner(owner) +{ + if (!mFilename.endsWith(QLatin1String(".db"))) + mFilename += QLatin1String(".db"); + + mMainSyncInterval = jsondbSettings->syncInterval(); + if (mMainSyncInterval < 1000) + mMainSyncInterval = 5000; + mIndexSyncInterval = jsondbSettings->indexSyncInterval(); + if (mIndexSyncInterval < 1000) + mIndexSyncInterval = 12000; +} + +JsonDbPartition::~JsonDbPartition() +{ + if (mTransactionDepth) { + qCritical() << "JsonDbBtreePartition::~JsonDbBtreePartition" + << "closing while transaction open" << "mTransactionDepth" << mTransactionDepth; + } + close(); +} + +bool JsonDbPartition::close() +{ + foreach (JsonDbView *view, mViews.values()) { + view->close(); + delete view; + } + mViews.clear(); + + delete mObjectTable; + mObjectTable = 0; + + return true; +} + +bool JsonDbPartition::open() +{ + if (jsondbSettings->debug()) + qDebug() << "JsonDbBtree::open" << mPartitionName << mFilename; + + mObjectTable = new JsonDbObjectTable(this); + mObjectTable->open(mFilename); + + if (!checkStateConsistency()) { + qCritical() << "JsonDbBtreePartition::open()" << "Unable to recover database"; + return false; + } + + bool rebuildingDatabaseMetadata = false; + + QString partitionId; + GetObjectsResult getObjectsResult = mObjectTable->getObjects(JsonDbString::kTypeStr, QJsonValue(JsonDbString::kDbidTypeStr), + JsonDbString::kDbidTypeStr); + if (getObjectsResult.data.size()) { + QJsonObject object = getObjectsResult.data.at(0); + partitionId = object.value("id").toString(); + QString partitionName = object.value("name").toString(); + if (partitionName != mPartitionName || !object.contains(JsonDbString::kDatabaseSchemaVersionStr) + || object.value(JsonDbString::kDatabaseSchemaVersionStr).toString() != gDatabaseSchemaVersion) { + if (jsondbSettings->verbose()) + qDebug() << "Rebuilding database metadata"; + rebuildingDatabaseMetadata = true; + } + } + + if (partitionId.isEmpty() || rebuildingDatabaseMetadata) { + if (partitionId.isEmpty()) + partitionId = QUuid::createUuid().toString(); + + QByteArray baKey(4, 0); + qToBigEndian(0, (uchar *)baKey.data()); + + JsonDbObject object; + object.insert(JsonDbString::kTypeStr, JsonDbString::kDbidTypeStr); + object.insert(QLatin1String("id"), partitionId); + object.insert(QLatin1String("name"), mPartitionName); + object.insert(JsonDbString::kDatabaseSchemaVersionStr, gDatabaseSchemaVersion); + JsonDbWriteResult result = updateObject(mDefaultOwner, object, ForcedWrite); + Q_ASSERT(result.code == JsonDbError::NoError); + } + if (jsondbSettings->verbose()) + qDebug() << "partition" << mPartitionName << "id" << partitionId; + + initSchemas(); + initIndexes(); + JsonDbView::initViews(this); + + return true; +} + +bool JsonDbPartition::clear() +{ + if (mObjectTable->bdb()) { + qCritical() << "Cannot clear database while it is open."; + return false; + } + QStringList filters; + QFileInfo fi(mFilename); + filters << QString::fromLatin1("%1*.db").arg(fi.baseName()); + QDir dir(fi.absolutePath()); + QStringList lst = dir.entryList(filters); + foreach (const QString &fileName, lst) { + if (jsondbSettings->verbose()) + qDebug() << "removing" << fileName; + if (!dir.remove(fileName)) { + qCritical() << "Failed to remove" << fileName; + return false; + } + } + return true; +} + +inline quint16 fieldValueSize(QJsonValue::Type vt, const QJsonValue &fieldValue) +{ + switch (vt) { + case QJsonValue::Undefined: + case QJsonValue::Null: + case QJsonValue::Array: + case QJsonValue::Object: + return 0; + case QJsonValue::Bool: + return 4; + case QJsonValue::Double: + return 8; + case QJsonValue::String: + return 2*fieldValue.toString().count(); + } + return 0; +} + +void memcpyFieldValue(char *data, QJsonValue::Type vt, const QJsonValue &fieldValue) +{ + switch (vt) { + case QJsonValue::Undefined: + case QJsonValue::Null: + case QJsonValue::Array: + case QJsonValue::Object: + break; + case QJsonValue::Bool: { + quint32 value = fieldValue.toBool() ? 1 : 0; + qToBigEndian(value, (uchar *)data); + } break; + case QJsonValue::Double: { + union { + double d; + quint64 ui; + }; + d = fieldValue.toDouble(); + qToBigEndian<quint64>(ui, (uchar *)data); + } break; + case QJsonValue::String: { + QString str = fieldValue.toString(); + memcpy(data, (const char *)str.constData(), 2*str.count()); + } + } +} + +void memcpyFieldValue(QJsonValue::Type vt, QJsonValue &fieldValue, const char *data, quint16 size) +{ + switch (vt) { + case QJsonValue::Undefined: + case QJsonValue::Array: + case QJsonValue::Object: + break; + case QJsonValue::Null: + fieldValue = QJsonValue(); + break; + case QJsonValue::Bool: { + fieldValue = qFromBigEndian<qint32>((const uchar *)data) == 1 ? true : false; + } break; + case QJsonValue::Double: { + union { + double d; + quint64 ui; + }; + ui = qFromBigEndian<quint64>((const uchar *)data); + fieldValue = d; + } break; + case QJsonValue::String: { + fieldValue = QString((const QChar *)data, size/2); + } + } +} + +int intcmp(const uchar *aptr, const uchar *bptr) +{ + qint32 a = qFromBigEndian<qint32>((const uchar *)aptr); + qint32 b = qFromBigEndian<qint32>((const uchar *)bptr); + if (a < b) + return -1; + if (a > b) + return 1; + return 0; +} +int doublecmp(const uchar *aptr, const uchar *bptr) +{ + union { + double d; + quint64 ui; + } a, b; + a.ui = qFromBigEndian<quint64>((const uchar *)aptr); + b.ui = qFromBigEndian<quint64>((const uchar *)bptr); + if (a.d < b.d) + return -1; + if (a.d > b.d) + return 1; + return 0; +} +int qstringcmp(const quint16 *achar, quint32 acount, const quint16 *bchar, quint32 bcount) +{ + int rv = 0; + quint32 minCount = qMin(acount, bcount); + for (quint32 i = 0; i < minCount; i++) { + if ((rv = (achar[i] - bchar[i])) != 0) + return rv; + } + return acount-bcount; +} + +QJsonValue makeFieldValue(const QJsonValue &value, const QString &type) +{ + if (type.isEmpty() || type == QLatin1String("string")) { + switch (value.type()) { + case QJsonValue::Null: return QLatin1String("null"); + case QJsonValue::Bool: return QLatin1String(value.toBool() ? "true" : "false"); + case QJsonValue::Double: return QString::number(value.toDouble()); + case QJsonValue::String: return value.toString(); + case QJsonValue::Array: { + QJsonArray array = value.toArray(); + if (array.size() == 1) + return makeFieldValue(array.at(0), type); + return QJsonValue(QJsonValue::Undefined); + } + case QJsonValue::Object: break; + case QJsonValue::Undefined: break; + } + } else if ((type == QLatin1String("number")) + || (type == QLatin1String("integer"))) { + switch (value.type()) { + case QJsonValue::Null: return 0; + case QJsonValue::Bool: return value.toBool() ? 1 : 0; + case QJsonValue::Double: return value.toDouble(); + case QJsonValue::String: { + QString str = value.toString(); + bool ok = false; + double dval = str.toDouble(&ok); + if (ok) + return dval; + int ival = str.toInt(&ok); + if (ok) + return ival; + break; + } + case QJsonValue::Array: { + QJsonArray array = value.toArray(); + if (array.size() == 1) + return makeFieldValue(array.at(0), type); + return QJsonValue(QJsonValue::Undefined); + } + case QJsonValue::Object: break; + case QJsonValue::Undefined: break; + } + } else { + qWarning() << "qtjsondb: makeFieldValue: unsupported index type" << type; + } + return QJsonValue(QJsonValue::Undefined); +} + +static const int VtLastKind = QJsonValue::Undefined; +QByteArray makeForwardKey(const QJsonValue &fieldValue, const ObjectKey &objectKey) +{ + QJsonValue::Type vt = fieldValue.type(); + Q_ASSERT(vt <= VtLastKind); + quint32 size = fieldValueSize(vt, fieldValue); + + QByteArray forwardKey(4+size+16, 0); + char *data = forwardKey.data(); + qToBigEndian<quint32>(vt, (uchar *)&data[0]); + memcpyFieldValue(data+4, vt, fieldValue); + qToBigEndian(objectKey, (uchar *)&data[4+size]); + + return forwardKey; +} + +void forwardKeySplit(const QByteArray &forwardKey, QJsonValue &fieldValue) +{ + const char *data = forwardKey.constData(); + QJsonValue::Type vt = (QJsonValue::Type)qFromBigEndian<quint32>((const uchar *)&data[0]); + Q_ASSERT(vt <= VtLastKind); + quint32 fvSize = forwardKey.size()-4-16; + memcpyFieldValue(vt, fieldValue, data+4, fvSize); +} +void forwardKeySplit(const QByteArray &forwardKey, QJsonValue &fieldValue, ObjectKey &objectKey) +{ + const char *data = forwardKey.constData(); + QJsonValue::Type vt = (QJsonValue::Type)qFromBigEndian<quint32>((const uchar *)&data[0]); + Q_ASSERT(vt <= VtLastKind); + quint32 fvSize = forwardKey.size()-4-16; + memcpyFieldValue(vt, fieldValue, data+4, fvSize); + objectKey = qFromBigEndian<ObjectKey>((const uchar *)&data[4+fvSize]); +} +int forwardKeyCmp(const QByteArray &ab, const QByteArray &bb) +{ + const char *aptr = ab.constData(); + size_t asiz = ab.size(); + const char *bptr = bb.constData(); + size_t bsiz = bb.size(); + + if (!bsiz && !asiz) + return 0; + if (!bsiz) + return 1; + if (!asiz) + return -1; + + int rv = 0; + QJsonValue::Type avt = (QJsonValue::Type)qFromBigEndian<quint32>((const uchar *)&aptr[0]); + QJsonValue::Type bvt = (QJsonValue::Type)qFromBigEndian<quint32>((const uchar *)&bptr[0]); + Q_ASSERT(avt <= VtLastKind); + Q_ASSERT(bvt <= VtLastKind); + quint32 asize = asiz - 4 - 16; + quint32 bsize = bsiz - 4 - 16; + if (avt != bvt) + return avt - bvt; + + const char *aData = aptr + 4; + const char *bData = bptr + 4; + switch (avt) { + case QJsonValue::Bool: + rv = intcmp((const uchar *)aData, (const uchar *)bData); + break; + case QJsonValue::Double: + rv = doublecmp((const uchar *)aData, (const uchar *)bData); + break; + case QJsonValue::String: + rv = qstringcmp((const quint16 *)aData, asize/2, (const quint16 *)bData, bsize/2); + break; + case QJsonValue::Undefined: + case QJsonValue::Null: + case QJsonValue::Array: + case QJsonValue::Object: + rv = 0; + break; + } + if (rv != 0) + return rv; + ObjectKey aObjectKey = qFromBigEndian<ObjectKey>((const uchar *)aptr+4+asize); + ObjectKey bObjectKey = qFromBigEndian<ObjectKey>((const uchar *)bptr+4+bsize); + if (aObjectKey == bObjectKey) + return 0; + return aObjectKey < bObjectKey ? -1 : 1; +} + +QByteArray makeForwardValue(const ObjectKey &objectKey) +{ + QByteArray forwardValue(16, 0); + char *data = forwardValue.data(); + qToBigEndian(objectKey, (uchar *)&data[0]); + return forwardValue; +} +void forwardValueSplit(const QByteArray &forwardValue, ObjectKey &objectKey) +{ + const uchar *data = (const uchar *)forwardValue.constData(); + objectKey = qFromBigEndian<ObjectKey>(&data[0]); +} + +JsonDbView *JsonDbPartition::addView(const QString &viewType) +{ + JsonDbView *view = mViews.value(viewType); + if (view) + return view; + + view = new JsonDbView(this, viewType, this); + view->open(); + mViews.insert(viewType, view); + return view; +} + +void JsonDbPartition::removeView(const QString &viewType) +{ + JsonDbView *view = mViews.value(viewType); + view->close(); + view->deleteLater(); + mViews.remove(viewType); +} + +void JsonDbPartition::updateView(const QString &objectType, quint32 stateNumber) +{ + if (!mViews.contains(objectType)) + return; + mViews[objectType]->updateView(stateNumber); +} + +bool JsonDbPartition::checkCanAddSchema(const JsonDbObject &schema, const JsonDbObject &oldSchema, QString &errorMsg) +{ + if (!schema.contains("name") || !schema.contains("schema")) { + errorMsg = QLatin1String("_schemaType objects must specify both name and schema properties"); + return false; + } + + QString schemaName = schema.value("name").toString(); + + if (schemaName.isEmpty()) { + errorMsg = QLatin1String("name property of _schemaType object must be specified"); + return false; + } else if (mSchemas.contains(schemaName) && oldSchema.value("name").toString() != schemaName) { + errorMsg = QString("A schema with name %1 already exists").arg(schemaName); + return false; + } + + return true; +} + +JsonDbView *JsonDbPartition::findView(const QString &objectType) const +{ + if (mViews.contains(objectType)) + return mViews.value(objectType); + else + return 0; +} + +JsonDbObjectTable *JsonDbPartition::findObjectTable(const QString &objectType) const +{ + if (mViews.contains(objectType)) + return mViews.value(objectType)->objectTable(); + else + return mObjectTable; +} + +bool JsonDbPartition::beginTransaction() +{ + if (mTransactionDepth++ == 0) { + Q_ASSERT(mTableTransactions.isEmpty()); + } + return true; +} + +bool JsonDbPartition::commitTransaction(quint32 stateNumber) +{ + if (--mTransactionDepth == 0) { + bool ret = true; + quint32 nextStateNumber = stateNumber ? stateNumber : (mObjectTable->stateNumber() + 1); + + if (jsondbSettings->debug()) + qDebug() << "commitTransaction" << stateNumber; + + if (!stateNumber && (mTableTransactions.size() == 1)) + nextStateNumber = mTableTransactions.at(0)->stateNumber() + 1; + + for (int i = 0; i < mTableTransactions.size(); i++) { + JsonDbObjectTable *table = mTableTransactions.at(i); + if (!table->commit(nextStateNumber)) { + qCritical() << __FILE__ << __LINE__ << "Failed to commit transaction on object table"; + ret = false; + } + } + mTableTransactions.clear(); + + if (mMainSyncTimerId == -1) + mMainSyncTimerId = startTimer(mMainSyncInterval, Qt::VeryCoarseTimer); + if (mIndexSyncTimerId == -1) + mIndexSyncTimerId = startTimer(mIndexSyncInterval, Qt::VeryCoarseTimer); + + return ret; + } + return true; +} + +bool JsonDbPartition::abortTransaction() +{ + if (--mTransactionDepth == 0) { + if (jsondbSettings->verbose()) + qDebug() << "JsonDbBtreePartition::abortTransaction()"; + bool ret = true; + + for (int i = 0; i < mTableTransactions.size(); i++) { + JsonDbObjectTable *table = mTableTransactions.at(i); + if (!table->abort()) { + qCritical() << __FILE__ << __LINE__ << "Failed to abort transaction"; + ret = false; + } + } + mTableTransactions.clear(); + return ret; + } + return true; +} + +QJsonObject JsonDbPartition::flush() +{ + mObjectTable->sync(JsonDbObjectTable::SyncObjectTable); + + QJsonObject resultmap, errormap; + resultmap.insert(JsonDbString::kStateNumberStr, (int)mObjectTable->stateNumber()); + return makeResponse(resultmap, errormap); +} + + +void JsonDbPartition::timerEvent(QTimerEvent *event) +{ + if (mTransactionDepth) + return; + + if (event->timerId() == mMainSyncTimerId) { + if (jsondbSettings->debug()) + qDebug() << "Syncing main object table"; + + mObjectTable->sync(JsonDbObjectTable::SyncObjectTable); + killTimer(mMainSyncTimerId); + mMainSyncTimerId = -1; + } else if (event->timerId() == mIndexSyncTimerId) { + + if (jsondbSettings->debug()) + qDebug() << "Syncing indexes and views"; + + // sync the main object table's indexes + mObjectTable->sync(JsonDbObjectTable::SyncIndexes); + + // sync the views + foreach (JsonDbView *view, mViews) + view->objectTable()->sync(JsonDbObjectTable::SyncObjectTable | JsonDbObjectTable::SyncIndexes); + + killTimer(mIndexSyncTimerId); + mIndexSyncTimerId = -1; + } +} + +bool JsonDbPartition::getObject(const QString &uuid, JsonDbObject &object, const QString &objectType) const +{ + ObjectKey objectKey(uuid); + return getObject(objectKey, object, objectType); +} + +bool JsonDbPartition::getObject(const ObjectKey &objectKey, JsonDbObject &object, const QString &objectType) const +{ + JsonDbObjectTable *table = findObjectTable(objectType); + + bool ok = table->get(objectKey, &object); + if (ok) + return ok; + QHash<QString,QPointer<JsonDbView> >::const_iterator it = mViews.begin(); + for (; it != mViews.end(); ++it) { + JsonDbView *view = it.value(); + if (!view) + qDebug() << "no view"; + if (!view->objectTable()) + qDebug() << "no object table for view"; + bool ok = view->objectTable()->get(objectKey, &object); + if (ok) + return ok; + } + return false; +} + +GetObjectsResult JsonDbPartition::getObjects(const QString &keyName, const QJsonValue &keyValue, const QString &_objectType, bool updateViews) +{ + const QString &objectType = (keyName == JsonDbString::kTypeStr) ? keyValue.toString() : _objectType; + JsonDbObjectTable *table = findObjectTable(objectType); + + if (updateViews && (table != mObjectTable)) + updateView(objectType); + return table->getObjects(keyName, keyValue, objectType); +} + +QJsonObject JsonDbPartition::changesSince(quint32 stateNumber, const QSet<QString> &limitTypes) +{ + JsonDbObjectTable *objectTable = 0; + if (!limitTypes.size()) + objectTable = mObjectTable; + else + foreach (const QString &limitType, limitTypes) { + JsonDbObjectTable *ot = findObjectTable(limitType); + if (!objectTable) + objectTable = ot; + else if (ot == objectTable) + continue; + else + return JsonDbPartition::makeError(JsonDbError::InvalidRequest, "limit types must be from the same object table"); + } + Q_ASSERT(objectTable); + JsonDbUpdateList changeList; + quint32 currentStateNumber = objectTable->changesSince(stateNumber, limitTypes, &changeList); + QJsonArray changeArray; + foreach (const JsonDbUpdate &update, changeList) { + QJsonObject change; + change.insert("before", update.oldObject); + change.insert("after", update.newObject); + changeArray.append(change); + } + + QJsonObject resultmap, errormap; + resultmap.insert("count", changeArray.size()); + resultmap.insert("startingStateNumber", static_cast<qint32>(stateNumber)); + resultmap.insert("currentStateNumber", static_cast<qint32>(currentStateNumber)); + resultmap.insert("changes", changeArray); + QJsonObject changesSince(JsonDbPartition::makeResponse(resultmap, errormap)); + return changesSince; +} + +void JsonDbPartition::flushCaches() +{ + mObjectTable->flushCaches(); + for (QHash<QString,QPointer<JsonDbView> >::const_iterator it = mViews.begin(); + it != mViews.end(); + ++it) + it.value()->reduceMemoryUsage(); +} + +void JsonDbPartition::initIndexes() +{ + QByteArray baPropertyName; + QByteArray baIndexObject; + + mObjectTable->addIndexOnProperty(JsonDbString::kUuidStr, "string"); + mObjectTable->addIndexOnProperty(JsonDbString::kTypeStr, "string"); + + GetObjectsResult getObjectsResult = mObjectTable->getObjects(JsonDbString::kTypeStr, JsonDbString::kIndexTypeStr, JsonDbString::kIndexTypeStr); + foreach (const QJsonObject indexObject, getObjectsResult.data) { + if (jsondbSettings->verbose()) + qDebug() << "initIndexes" << "index" << indexObject; + QString indexObjectType = indexObject.value(JsonDbString::kTypeStr).toString(); + if (indexObjectType == JsonDbString::kIndexTypeStr) { + QString indexName = JsonDbIndex::determineName(indexObject); + QString propertyName = indexObject.value(JsonDbString::kPropertyNameStr).toString(); + QString propertyType = indexObject.value(JsonDbString::kPropertyTypeStr).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, objectTypes, propertyFunction, locale, collation, casePreference, caseSensitivity); + } + } +} + +bool JsonDbPartition::addIndex(const QString &indexName, const QString &propertyName, + 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 = 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, objectTypes, propertyFunction, locale, collation, casePreference, caseSensitivity); +} + +bool JsonDbPartition::removeIndex(const QString &indexName, const QString &objectType) +{ + JsonDbObjectTable *table = findObjectTable(objectType); + const IndexSpec *indexSpec = table->indexSpec(indexName); + if (!indexSpec) + return false; + return table->removeIndex(indexName); +} + +bool JsonDbPartition::checkStateConsistency() +{ + return true; +} + +void JsonDbPartition::checkIndexConsistency(JsonDbObjectTable *objectTable, JsonDbIndex *index) +{ + quint32 indexStateNumber = index->bdb()->tag(); + quint32 objectStateNumber = objectTable->stateNumber(); + if (indexStateNumber > objectTable->stateNumber()) { + qCritical() << "reverting index" << index->propertyName() << indexStateNumber << objectStateNumber; + while (indexStateNumber > objectTable->stateNumber()) { + int rc = index->bdb()->rollback(); + quint32 newIndexStateNumber = index->bdb()->tag(); + if (newIndexStateNumber == indexStateNumber) { + qDebug() << "failed to revert. clearing" << rc; + index->bdb()->clearData(); + break; + } + qCritical() << " reverted index to state" << indexStateNumber; + } + } +} + +QHash<QString, qint64> JsonDbPartition::fileSizes() const +{ + QList<QFileInfo> fileInfo; + fileInfo << mObjectTable->bdb()->fileName(); + + foreach (const IndexSpec &spec, mObjectTable->indexSpecs().values()) { + if (spec.index->bdb()) + fileInfo << spec.index->bdb()->fileName(); + } + + foreach (JsonDbView *view, mViews) { + JsonDbObjectTable *objectTable = view->objectTable(); + fileInfo << objectTable->bdb()->fileName(); + foreach (const IndexSpec &spec, objectTable->indexSpecs().values()) { + if (spec.index->bdb()) + fileInfo << spec.index->bdb()->fileName(); + } + } + + QHash<QString, qint64> result; + foreach (const QFileInfo &info, fileInfo) + result.insert(info.fileName(), info.size()); + return result; +} + +void JsonDbPartition::compileOrQueryTerm(JsonDbIndexQuery *indexQuery, const QueryTerm &queryTerm) +{ + QString op = queryTerm.op(); + QJsonValue fieldValue = queryTerm.value(); + if (op == ">") { + indexQuery->addConstraint(new QueryConstraintGt(fieldValue)); + indexQuery->setMin(fieldValue); + } else if (op == ">=") { + indexQuery->addConstraint(new QueryConstraintGe(fieldValue)); + indexQuery->setMin(fieldValue); + } else if (op == "<") { + indexQuery->addConstraint(new QueryConstraintLt(fieldValue)); + indexQuery->setMax(fieldValue); + } else if (op == "<=") { + indexQuery->addConstraint(new QueryConstraintLe(fieldValue)); + indexQuery->setMax(fieldValue); + } else if (op == "=") { + indexQuery->addConstraint(new QueryConstraintEq(fieldValue)); + indexQuery->setMin(fieldValue); + indexQuery->setMax(fieldValue); + } else if (op == "=~") { + const QRegExp &re = queryTerm.regExpConst(); + QRegExp::PatternSyntax syntax = re.patternSyntax(); + Qt::CaseSensitivity cs = re.caseSensitivity(); + QString pattern = re.pattern(); + indexQuery->addConstraint(new QueryConstraintRegExp(re)); + if (cs == Qt::CaseSensitive) { + QString prefix; + if ((syntax == QRegExp::Wildcard) + && mWildCardPrefixRegExp.exactMatch(pattern)) { + prefix = mWildCardPrefixRegExp.cap(1); + if (jsondbSettings->debug()) + qDebug() << "wildcard regexp prefix" << pattern << prefix; + } + indexQuery->setMin(prefix); + indexQuery->setMax(prefix); + } + } else if (op == "!=") { + indexQuery->addConstraint(new QueryConstraintNe(fieldValue)); + } else if (op == "exists") { + indexQuery->addConstraint(new QueryConstraintExists); + } else if (op == "notExists") { + indexQuery->addConstraint(new QueryConstraintNotExists); + } else if (op == "in") { + QJsonArray value = queryTerm.value().toArray(); + //qDebug() << "in" << value << value.size(); + if (value.size() == 1) + indexQuery->addConstraint(new QueryConstraintEq(value.at(0))); + else + indexQuery->addConstraint(new QueryConstraintIn(queryTerm.value())); + } else if (op == "notIn") { + indexQuery->addConstraint(new QueryConstraintNotIn(queryTerm.value())); + } else if (op == "in") { + indexQuery->addConstraint(new QueryConstraintContains(queryTerm.value())); + } else if (op == "startsWith") { + indexQuery->addConstraint(new QueryConstraintStartsWith(queryTerm.value().toString())); + } +} + +JsonDbIndexQuery *JsonDbPartition::compileIndexQuery(const JsonDbOwner *owner, const JsonDbQuery *query) +{ + JsonDbIndexQuery *indexQuery = 0; + JsonDbQuery *residualQuery = new JsonDbQuery(); + QString orderField; + QSet<QString> typeNames; + const QList<OrderTerm> &orderTerms = query->orderTerms; + const QList<OrQueryTerm> &orQueryTerms = query->queryTerms; + QString indexCandidate; + 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(); + if (querypropertyNames.size() == 1) { + //QString fieldValue = queryTerm.value().toString(); + QString propertyName = querypropertyNames[0]; + + const QList<QueryTerm> &queryTerms = orQueryTerm.terms(); + const QueryTerm &queryTerm = queryTerms[0]; + + if ((typeNames.size() == 1) + && mViews.contains(typeNames.toList()[0])) { + view = mViews[typeNames.toList()[0]]; + table = view->objectTable(); + } + + if (table->indexSpec(propertyName)) + indexedQueryTermCount++; + else if (indexCandidate.isEmpty() + && (propertyName != JsonDbString::kTypeStr) + && !unindexablePropertyNames.contains(propertyName)) { + indexCandidate = propertyName; + if (!queryTerm.joinField().isEmpty()) + indexCandidate = queryTerm.joinPaths()[0].join("->"); + } + + propertyName = queryTerm.propertyName(); + QString fieldValue = queryTerm.value().toString(); + QString op = queryTerm.op(); + if (propertyName == JsonDbString::kTypeStr) { + if ((op == "=") || (op == "in")) { + QSet<QString> types; + if (op == "=") { + types << fieldValue; + for (int i = 1; i < queryTerms.size(); ++i) { + if (queryTerms[i].propertyName() == JsonDbString::kTypeStr && queryTerms[i].op() == "=") + types << queryTerms[i].value().toString(); + } + } else { + QJsonArray array = queryTerm.value().toArray(); + types.clear(); + for (int t = 0; t < array.size(); t++) + types << array.at(t).toString(); + } + + if (typeNames.count()) { + typeNames.intersect(types); + if (!typeNames.count()) { + // make this a null query -- I really need a domain (partial order) here and not a set + typeNames = types; + } + } else { + typeNames = types; + } + } else if ((op == "!=") || (op == "notIn")) { + QSet<QString> types; + if (op == "!=") + types << fieldValue; + else { + QJsonArray array = queryTerm.value().toArray(); + for (int t = 0; t < array.size(); t++) + types << array.at(t).toString(); + } + } + } + } + } + } + if ((typeNames.size() == 1) + && mViews.contains(typeNames.toList()[0])) { + view = mViews[typeNames.toList()[0]]; + table = view->objectTable(); + } + if (!indexedQueryTermCount && !indexCandidate.isEmpty()) { + if (jsondbSettings->debug()) + qDebug() << "adding index" << indexCandidate; + //TODO: remove this + table->addIndexOnProperty(indexCandidate); + } + + for (int i = 0; i < orderTerms.size(); i++) { + const OrderTerm &orderTerm = orderTerms[i]; + QString propertyName = orderTerm.propertyName; + if (!table->indexSpec(propertyName)) { + if (jsondbSettings->verbose() || jsondbSettings->performanceLog()) + qDebug() << "Unindexed sort term" << propertyName << orderTerm.ascending; + 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; + const IndexSpec *indexSpec = table->indexSpec(propertyName); + if (view) + view->updateView(); + + indexQuery = JsonDbIndexQuery::indexQuery(this, table, propertyName, indexSpec->propertyType, + owner, orderTerm.ascending); + } else if (orderField != propertyName) { + qCritical() << QString("unimplemented: multiple order terms. Sorting on '%1'").arg(orderField); + residualQuery->orderTerms.append(orderTerm); + } + } + + for (int i = 0; i < orQueryTerms.size(); i++) { + const OrQueryTerm &orQueryTerm = orQueryTerms[i]; + const QList<QueryTerm> &queryTerms = orQueryTerm.terms(); + if (queryTerms.size() == 1) { + QueryTerm queryTerm = queryTerms[0]; + QString propertyName = queryTerm.propertyName(); + QString fieldValue = queryTerm.value().toString(); + QString op = queryTerm.op(); + + if (!queryTerm.joinField().isEmpty()) { + residualQuery->queryTerms.append(queryTerm); + propertyName = queryTerm.joinField(); + op = "exists"; + queryTerm.setPropertyName(propertyName); + queryTerm.setOp(op); + queryTerm.setJoinField(QString()); + } + if (!table->indexSpec(propertyName) + || (indexQuery + && (propertyName != orderField))) { + if (jsondbSettings->verbose() || jsondbSettings->debug()) + qDebug() << "residual query term" << propertyName << "orderField" << orderField; + residualQuery->queryTerms.append(queryTerm); + continue; + } + + if (!indexQuery + && (propertyName != JsonDbString::kTypeStr) + && table->indexSpec(propertyName) + && !unindexablePropertyNames.contains(propertyName)) { + orderField = propertyName; + const IndexSpec *indexSpec = table->indexSpec(propertyName); + if (view) + view->updateView(); + indexQuery = JsonDbIndexQuery::indexQuery(this, table, propertyName, indexSpec->propertyType, owner); + } + + if (propertyName == orderField) { + compileOrQueryTerm(indexQuery, queryTerm); + } else { + residualQuery->queryTerms.append(orQueryTerm); + } + } else { + residualQuery->queryTerms.append(orQueryTerm); + } + } + + if (!indexQuery) { + QString defaultIndex = JsonDbString::kUuidStr; + if (typeNames.size()) { + if ((typeNames.size() == 1) + && mViews.contains(typeNames.toList()[0])) { + view = mViews[typeNames.toList()[0]]; + table = view->objectTable(); + } else + defaultIndex = JsonDbString::kTypeStr; + } + const IndexSpec *indexSpec = table->indexSpec(defaultIndex); + + //qDebug() << "defaultIndex" << defaultIndex << "on table" << indexSpec->objectType; + + if (view) + view->updateView(); + indexQuery = JsonDbIndexQuery::indexQuery(this, table, defaultIndex, indexSpec->propertyType, owner); + if (typeNames.size() == 0) + qCritical() << "searching all objects" << query->query; + + if (defaultIndex == JsonDbString::kTypeStr) { + foreach (const OrQueryTerm &term, orQueryTerms) { + QList<QueryTerm> terms = term.terms(); + if (terms.size() == 1 && terms[0].propertyName() == JsonDbString::kTypeStr) { + compileOrQueryTerm(indexQuery, terms[0]); + break; + } + } + } + } + if (typeNames.count() > 0) + indexQuery->setTypeNames(typeNames); + if (residualQuery->queryTerms.size() || residualQuery->orderTerms.size()) + indexQuery->setResidualQuery(residualQuery); + else + delete residualQuery; + indexQuery->setAggregateOperation(query->mAggregateOperation); + indexQuery->setResultExpressionList(query->mapExpressionList); + indexQuery->setResultKeyList(query->mapKeyList); + return indexQuery; +} + +void JsonDbPartition::doIndexQuery(const JsonDbOwner *owner, JsonDbObjectList &results, int &limit, int &offset, + JsonDbIndexQuery *indexQuery) +{ + if (jsondbSettings->debugQuery()) + qDebug() << "doIndexQuery" << "limit" << limit << "offset" << offset; + + bool countOnly = (indexQuery->aggregateOperation() == "count"); + int count = 0; + for (JsonDbObject object = indexQuery->first(); + !object.isEmpty(); + object = indexQuery->next()) { + if (!owner->isAllowed(object, indexQuery->partition(), QString("read"))) + continue; + if (limit && (offset <= 0)) { + if (!countOnly) { + if (jsondbSettings->debugQuery()) + qDebug() << "appending result" << object << endl; + JsonDbObject result = indexQuery->resultObject(object); + results.append(result); + } + limit--; + count++; + } + offset--; + if (limit == 0) + break; + } + if (countOnly) { + QJsonObject countObject; + countObject.insert(QLatin1String("count"), count); + results.append(countObject); + } +} + +bool JsonDbPartition::checkQuota(const JsonDbOwner *owner, int size) const +{ + Q_UNUSED(owner); + Q_UNUSED(size); + return true; +} + +bool JsonDbPartition::addToQuota(const JsonDbOwner *owner, int size) +{ + Q_UNUSED(owner); + Q_UNUSED(size); + return true; +} + +JsonDbQueryResult JsonDbPartition::queryObjects(const JsonDbOwner *owner, const JsonDbQuery *query, int limit, int offset) +{ + JsonDbQueryResult result; + 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); + + int elapsedToCompile = time.elapsed(); + doIndexQuery(owner, results, limit, offset, indexQuery); + int elapsedToQuery = time.elapsed(); + quint32 stateNumber = indexQuery->stateNumber(); + int length = results.size(); + JsonDbQuery *residualQuery = indexQuery->residualQuery(); + if (residualQuery && residualQuery->orderTerms.size()) { + if (jsondbSettings->verbose()) + qDebug() << "queryPersistentObjects" << "sorting"; + sortValues(residualQuery, results, joinedResults); + } + + QJsonArray sortKeys; + sortKeys.append(indexQuery->propertyName()); + sortKeys.append(indexQuery->objectTable()->filename()); + + delete indexQuery; + + QStringList mapExpressions = query->mapExpressionList; + QStringList mapKeys = query->mapKeyList; + + result.data = results; + result.length = length; + result.offset = offset; + result.state = (qint32)stateNumber; + result.sortKeys = sortKeys; + int elapsedToDone = time.elapsed(); + if (jsondbSettings->verbose()) + qDebug() << "elapsed" << elapsedToCompile << elapsedToQuery << elapsedToDone << query->query; + return result; +} + +JsonDbWriteResult JsonDbPartition::updateObjects(const JsonDbOwner *owner, const JsonDbObjectList &objects, JsonDbPartition::WriteMode mode, + JsonDbUpdateList *changeList) +{ + JsonDbWriteResult result; + WithTransaction transaction(this); + QList<JsonDbUpdate> updated; + QString errorMsg; + + foreach (const JsonDbObject &toUpdate, objects) { + JsonDbObject object = toUpdate; + + bool forRemoval = object.value(JsonDbString::kDeletedStr).toBool(); + + if (!object.contains(JsonDbString::kUuidStr) || object.value(JsonDbString::kUuidStr).isNull()) { + if (forRemoval) { + result.code = JsonDbError::MissingUUID; + result.message = QLatin1String("Missing '_uuid' field in object"); + return result; + } + + if (!object.contains(JsonDbString::kOwnerStr) + || ((object.value(JsonDbString::kOwnerStr).toString() != owner->ownerId()) + && !owner->isAllowed(object, mPartitionName, "setOwner"))) + object.insert(JsonDbString::kOwnerStr, owner->ownerId()); + object.generateUuid(); + } + + if (!forRemoval && object.value(JsonDbString::kTypeStr).toString().isEmpty()) { + result.code = JsonDbError::MissingType; + result.message = QLatin1String("Missing '_type' field in object"); + return result; + } + + if (!(mode == ViewObject || checkNaturalObjectType(object, errorMsg))) { + result.code = JsonDbError::MissingType; + result.message = errorMsg; + } + if (!(forRemoval || validateSchema(object.type(), object, errorMsg))) { + result.code = JsonDbError::FailedSchemaValidation; + result.message = errorMsg; + return result; + } + + JsonDbObjectTable *objectTable = findObjectTable(object.type()); + + if (mode != ViewObject) { + if (mViewTypes.contains(object.type())) { + result.code = JsonDbError::InvalidType; + result.message = QString("Cannot write object of view type '%1'").arg(object.value(JsonDbString::kTypeStr).toString()); + return result; + } + + transaction.addObjectTable(objectTable); + } + + JsonDbObject master; + bool forCreation = !getObject(object.uuid(), master, object.type()); + + // FIXME: explicity disallow changing _type + + if (mode != ReplicatedWrite && forCreation && forRemoval) { + result.code = JsonDbError::MissingObject; + result.message = QLatin1String("Cannot remove non-existing object"); + return result; + } + + if (!(forCreation || owner->isAllowed(master, mPartitionName, "write"))) { + result.code = JsonDbError::OperationNotPermitted; + result.message = QLatin1String("Access denied"); + return result; + } + + if (!master.value(JsonDbString::kOwnerStr).toString().isEmpty()) + object.insert(JsonDbString::kOwnerStr, master.value(JsonDbString::kOwnerStr)); + else if (object.value(JsonDbString::kOwnerStr).toString().isEmpty()) + object.insert(JsonDbString::kOwnerStr, owner->ownerId()); + + if (!(forRemoval || owner->isAllowed(object, mPartitionName, "write"))) { + result.code = JsonDbError::OperationNotPermitted; + result.message = QLatin1String("Access denied"); + return result; + } + + bool validWrite; + QString versionWritten; + JsonDbObject oldMaster = master; + + switch (mode) { + case OptimisticWrite: + validWrite = master.updateVersionOptimistic(object, &versionWritten); + break; + case ViewObject: + case ForcedWrite: + master = object; + versionWritten = master.computeVersion(); + validWrite = true; + break; + case ReplicatedWrite: + validWrite = master.updateVersionReplicating(object); + versionWritten = master.version(); + break; + default: + result.code = JsonDbError::InvalidRequest; + result.message = QLatin1String("Missing writeMode implementation."); + return result; + } + + if (!validWrite) { + if (mode == ReplicatedWrite) { + result.code = JsonDbError::InvalidRequest; + result.message = QLatin1String("Replication has reject your update for sanity reasons"); + } else { + if (jsondbSettings->debug()) + qDebug() << "Stale update detected - expected version:" << oldMaster.version() << object; + result.code = JsonDbError::UpdatingStaleVersion; + result.message = QString("Updating stale version of object. Expected version %1, received %2").arg(oldMaster.version()).arg(versionWritten); + } + return result; + } + + // recheck, it might just be a conflict removal + forRemoval = master.isDeleted(); + + if (master.type().isNull()) + master.insert(JsonDbString::kTypeStr, oldMaster.type()); + + bool isVisibleWrite = oldMaster.version() != master.version(); + + if (isVisibleWrite) { + JsonDbError::ErrorCode errorCode; + if (!(forRemoval || (errorCode = checkBuiltInTypeValidity(master, oldMaster, errorMsg)) == JsonDbError::NoError)) { + result.code = errorCode; + result.message = errorMsg; + return result; + } else if (oldMaster.type() == JsonDbString::kSchemaTypeStr && + !checkCanRemoveSchema(oldMaster, errorMsg)) { + 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; + } + } + + ObjectKey objectKey(master.uuid()); + + if (!forCreation) + objectTable->deindexObject(objectKey, oldMaster, objectTable->stateNumber()); + + if (!objectTable->put(objectKey, master)) { + result.code = JsonDbError::DatabaseError; + result.message = objectTable->errorMessage(); + } + + if (jsondbSettings->debug()) + qDebug() << "Wrote object" << objectKey << endl << master << endl << oldMaster; + + JsonDbNotification::Action action = JsonDbNotification::Update; + if (forRemoval) + action = JsonDbNotification::Delete; + else if (forCreation) + action = JsonDbNotification::Create; + + JsonDbUpdate change(oldMaster, master, action); + quint32 stateNumber = objectTable->storeStateChange(objectKey, change); + if (changeList) + changeList->append(change); + + if (!forRemoval) + objectTable->indexObject(objectKey, master, objectTable->stateNumber()); + + updateBuiltInTypes(master, oldMaster); + + result.state = stateNumber; + + + master.insert(JsonDbString::kVersionStr, versionWritten); + result.objectsWritten.append(master); + updated.append(change); + } + + transaction.commit(); + + emit objectsUpdated(updated); + return result; +} + +JsonDbWriteResult JsonDbPartition::updateObject(const JsonDbOwner *owner, const JsonDbObject &object, JsonDbPartition::WriteMode mode, JsonDbUpdateList *changeList) +{ + return updateObjects(owner, JsonDbObjectList() << object, mode, changeList); +} + +void JsonDbPartition::checkIndex(const QString &propertyName) +{ +// TODO + if (mObjectTable->indexSpec(propertyName)) + mObjectTable->indexSpec(propertyName)->index->checkIndex(); +} + +bool JsonDbPartition::compact() +{ + for (QHash<QString,QPointer<JsonDbView> >::const_iterator it = mViews.begin(); + it != mViews.end(); + ++it) { + it.value()->objectTable()->compact(); + } + bool result = true; + result &= mObjectTable->compact(); + return result; +} + +JsonDbStat JsonDbPartition::stat() const +{ + JsonDbStat result; + for (QHash<QString,QPointer<JsonDbView> >::const_iterator it = mViews.begin(); + it != mViews.end(); + ++it) { + result += it.value()->objectTable()->stat(); + } + result += mObjectTable->stat(); + return result; +} + +struct QJsonSortable { + QJsonValue key; + QJsonObject result; + QJsonObject joinedResult; +}; + +bool sortableLessThan(const QJsonSortable &a, const QJsonSortable &b) +{ + return JsonDbIndexQuery::lessThan(a.key, b.key); +} +bool sortableGreaterThan(const QJsonSortable &a, const QJsonSortable &b) +{ + return JsonDbIndexQuery::greaterThan(a.key, b.key); +} + +void JsonDbPartition::sortValues(const JsonDbQuery *parsedQuery, JsonDbObjectList &results, JsonDbObjectList &joinedResults) +{ + const QList<OrderTerm> &orderTerms = parsedQuery->orderTerms; + if (!orderTerms.size() || (results.size() < 2)) + return; + const OrderTerm &orderTerm0 = orderTerms[0]; + QString field0 = orderTerm0.propertyName; + bool ascending = orderTerm0.ascending; + QStringList path0 = field0.split('.'); + + if (orderTerms.size() == 1) { + QVector<QJsonSortable> valuesToSort(results.size()); + int resultsSize = results.size(); + int joinedResultsSize = joinedResults.size(); + + for (int i = 0; i < resultsSize; i++) { + QJsonSortable *p = &valuesToSort[i]; + JsonDbObject r = results.at(i); + p->key = r.propertyLookup(path0); + p->result = r; + if (joinedResultsSize > i) + p->joinedResult = joinedResults.at(i); + } + + if (ascending) + qStableSort(valuesToSort.begin(), valuesToSort.end(), sortableLessThan); + else + qStableSort(valuesToSort.begin(), valuesToSort.end(), sortableGreaterThan); + + results = JsonDbObjectList(); + joinedResults = JsonDbObjectList(); + for (int i = 0; i < resultsSize; i++) { + QJsonSortable *p = &valuesToSort[i]; + results.append(p->result); + if (joinedResultsSize > i) + joinedResults.append(p->joinedResult); + } + } else { + qCritical() << "Unimplemented: sorting on multiple keys or non-string keys"; + } +} + +bool JsonDbPartition::checkCanRemoveSchema(const JsonDbObject &schema, QString &message) +{ + QString schemaName = schema.value("name").toString(); + + // for View types, make sure no Maps or Reduces point at this type + // the call to getObject will have updated the view, so pending Map or Reduce object removes will be forced + JsonDbView *view = findView(schemaName); + if (view && view->isActive()) { + message = QString("An active view with targetType of %2 exists. You cannot remove the schema").arg(schemaName); + return false; + } + + // check if any objects exist + GetObjectsResult getObjectResponse = getObjects(JsonDbString::kTypeStr, schemaName); + + // for non-View types, if objects exist the schema cannot be removed + if (!mViewTypes.contains(schemaName)) { + if (getObjectResponse.data.size() != 0) { + message = QString("%1 object(s) of type %2 exist. You cannot remove the schema") + .arg(getObjectResponse.data.size()) + .arg(schemaName); + return false; + } + } + + return true; +} + +bool JsonDbPartition::validateSchema(const QString &schemaName, const JsonDbObject &object, QString &errorMsg) +{ + errorMsg.clear(); + + if (!jsondbSettings->validateSchemas()) { + if (jsondbSettings->debug()) + qDebug() << "Not validating schemas"; + return true; + } + + QJsonObject result = mSchemas.validate(schemaName, object); + if (!result.value(JsonDbString::kCodeStr).isNull()) { + errorMsg = result.value(JsonDbString::kMessageStr).toString(); + if (jsondbSettings->debug()) + qDebug() << "Schema validation error: " << errorMsg << object; + return false; + } + + return true; +} + +bool JsonDbPartition::checkNaturalObjectType(const JsonDbObject &object, QString &errorMsg) +{ + QString type = object.value(JsonDbString::kTypeStr).toString(); + if (mViewTypes.contains(type)) { + QByteArray str = QJsonDocument(object).toJson(); + errorMsg = QString("Cannot create/remove object of view type '%1': '%2'").arg(type).arg(QString::fromUtf8(str)); + return false; + } + + return true; +} + +JsonDbError::ErrorCode JsonDbPartition::checkBuiltInTypeAccessControl(bool forCreation, const JsonDbOwner *owner, const JsonDbObject &object, const JsonDbObject &oldObject, QString &errorMsg) +{ + if (!jsondbSettings->enforceAccessControl()) + return JsonDbError::NoError; + + 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(JsonDbString::kNameStr); + 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; + } + } else if (objectType == JsonDbString::kIndexTypeStr) { + + // Only the owner or admin can update Index + if (!forCreation) { + QJsonValue oldOwner = oldObject.value(JsonDbString::kOwnerStr); + if (owner->ownerId() != oldOwner.toString() && !owner->allowAll()) { + // Only admin (allowAll = true) can update Index:s owned by somebody else + errorMsg = QString::fromLatin1("Only admin can update Index:s not owned by itself"); + return JsonDbError::OperationNotPermitted; + } + } + + // Check that owner can read all objectTypes + QJsonValue objectTypeProperty = object.value(QLatin1String("objectType")); + JsonDbObject fake; // Just for access control + if (objectTypeProperty.isUndefined() || objectTypeProperty.isNull()) { + if (!owner->allowAll()) { + // Only admin (allowAll = true) can do Index:s without objectType + errorMsg = QString::fromLatin1("Only admin can do Index:s without objectType"); + return JsonDbError::OperationNotPermitted; + } + } else if (objectTypeProperty.isArray()) { + QJsonArray arr = objectTypeProperty.toArray(); + foreach (QJsonValue val, arr) { + fake.insert (JsonDbString::kOwnerStr, object.value(JsonDbString::kOwnerStr)); + fake.insert (JsonDbString::kTypeStr, val.toString()); + if (!owner->isAllowed(fake, mPartitionName, "read")) { + errorMsg = QString::fromLatin1("Access denied %1 in Index %2").arg(val.toString()). + arg(JsonDbIndex::determineName(object)); + return JsonDbError::OperationNotPermitted; + } + } + } else if (objectTypeProperty.isString()) { + fake.insert (JsonDbString::kOwnerStr, object.value(JsonDbString::kOwnerStr)); + fake.insert (JsonDbString::kTypeStr, objectTypeProperty.toString()); + if (!owner->isAllowed(fake, mPartitionName, "read")) { + errorMsg = QString::fromLatin1("Access denied %1 in Index %2").arg(objectTypeProperty.toString()). + arg(JsonDbIndex::determineName(object)); + return JsonDbError::OperationNotPermitted; + } + } else { + errorMsg = QString::fromLatin1("Invalid objectType in Index %1").arg(JsonDbIndex::determineName(object)); + return JsonDbError::InvalidIndexOperation; + } + } + return JsonDbError::NoError; +} + +JsonDbError::ErrorCode JsonDbPartition::checkBuiltInTypeValidity(const JsonDbObject &object, const JsonDbObject &oldObject, QString &errorMsg) +{ + QString objectType = object.value(JsonDbString::kTypeStr).toString(); + errorMsg.clear(); + + if (objectType == JsonDbString::kSchemaTypeStr && + !checkCanAddSchema(object, oldObject, errorMsg)) + return JsonDbError::InvalidSchemaOperation; + else if (objectType == JsonDbString::kMapTypeStr && + !JsonDbMapDefinition::validateDefinition(object, this, errorMsg)) + return JsonDbError::InvalidMap; + else if (objectType == JsonDbString::kReduceTypeStr && + !JsonDbReduceDefinition::validateDefinition(object, this, errorMsg)) + return JsonDbError::InvalidReduce; + else if (objectType == JsonDbString::kIndexTypeStr && !JsonDbIndex::validateIndex(object, oldObject, errorMsg)) + return JsonDbError::InvalidIndexOperation; + + return JsonDbError::NoError; +} + +void JsonDbPartition::updateBuiltInTypes(const JsonDbObject &object, const JsonDbObject &oldObject) +{ + if (oldObject.type() == JsonDbString::kIndexTypeStr) { + QString indexName = JsonDbIndex::determineName(oldObject); + removeIndex(indexName, oldObject.value(JsonDbString::kObjectTypeStr).toString()); + } + + if (object.type() == JsonDbString::kIndexTypeStr && !object.isDeleted()) { + QString indexName = JsonDbIndex::determineName(object); + + bool caseSensitivity = true; + 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(), + objectTypes, + object.value(JsonDbString::kPropertyFunctionStr).toString(), + object.value(JsonDbString::kLocaleStr).toString(), + object.value(JsonDbString::kCollationStr).toString(), + object.value(JsonDbString::kCasePreferenceStr).toString(), + caseSensitivity == true ? Qt::CaseSensitive : Qt::CaseInsensitive); + } + + if (oldObject.type() == JsonDbString::kSchemaTypeStr) + removeSchema(oldObject.value(JsonDbString::kNameStr).toString()); + + if (object.type() == JsonDbString::kSchemaTypeStr && + object.value(JsonDbString::kSchemaStr).type() == QJsonValue::Object + && !object.isDeleted()) + setSchema(object.value(JsonDbString::kNameStr).toString(), object.value(JsonDbString::kSchemaStr).toObject()); + + if (!oldObject.isEmpty() + && (oldObject.type() == JsonDbString::kMapTypeStr || oldObject.type() == JsonDbString::kReduceTypeStr)) + JsonDbView::removeDefinition(this, oldObject); + + if (!object.isDeleted() + && (object.type() == JsonDbString::kMapTypeStr || object.type() == JsonDbString::kReduceTypeStr) + && !(object.contains(JsonDbString::kActiveStr) && !object.value(JsonDbString::kActiveStr).toBool())) + JsonDbView::createDefinition(this, object); +} + +void JsonDbPartition::setSchema(const QString &schemaName, const QJsonObject &schema) +{ + if (jsondbSettings->verbose()) + qDebug() << "setSchema" << schemaName << schema; + + QJsonObject errors = mSchemas.insert(schemaName, schema); + + if (!errors.isEmpty()) { + qWarning() << "setSchema failed because of errors" << schemaName << schema; + qWarning() << errors; + // FIXME should we accept broken schemas? + } + + if (schema.contains("extends")) { + QJsonValue extendsValue = schema.value("extends"); + QString extendedSchemaName; + if (extendsValue.type() == QJsonValue::String) + extendedSchemaName = extendsValue.toString(); + else if ((extendsValue.type() == QJsonValue::Object) + && extendsValue.toObject().contains("$ref")) + extendedSchemaName = extendsValue.toObject().value("$ref").toString(); + if (extendedSchemaName == JsonDbString::kViewTypeStr) { + mViewTypes.insert(schemaName); + addView(schemaName); + if (jsondbSettings->verbose()) + qDebug() << "viewTypes" << mViewTypes; + } + } + if (schema.contains("properties")) + updateSchemaIndexes(schemaName, schema); +} + +void JsonDbPartition::removeSchema(const QString &schemaName) +{ + if (jsondbSettings->verbose()) + qDebug() << "removeSchema" << schemaName; + + if (mSchemas.contains(schemaName)) { + QJsonObject schema = mSchemas.take(schemaName); + + if (schema.contains("extends")) { + QJsonValue extendsValue = schema.value("extends"); + QString extendedSchemaName; + if (extendsValue.type() == QJsonValue::String) + extendedSchemaName = extendsValue.toString(); + else if ((extendsValue.type() == QJsonValue::Object) + && extendsValue.toObject().contains("$ref")) + extendedSchemaName = extendsValue.toObject().value("$ref").toString(); + + if (extendedSchemaName == JsonDbString::kViewTypeStr) { + mViewTypes.remove(schemaName); + removeView(schemaName); + } + } + } +} + +void JsonDbPartition::updateSchemaIndexes(const QString &schemaName, QJsonObject object, const QStringList &path) +{ + QJsonObject properties = object.value("properties").toObject(); + const QList<QString> keys = properties.keys(); + for (int i = 0; i < keys.size(); i++) { + const QString &k = keys[i]; + QJsonObject propertyInfo = properties.value(k).toObject(); + if (propertyInfo.contains("indexed")) { + QString propertyType = (propertyInfo.contains("type") ? propertyInfo.value("type").toString() : "string"); + QStringList kpath = path; + kpath << k; + QString propertyName = kpath.join("."); + addIndex(propertyName, propertyName, propertyType); + } + if (propertyInfo.contains("properties")) + updateSchemaIndexes(schemaName, propertyInfo, path + (QStringList() << k)); + } +} + +void JsonDbPartition::setError(QJsonObject &map, int code, const QString &message) +{ + map.insert(JsonDbString::kCodeStr, code); + map.insert(JsonDbString::kMessageStr, message); +} + +QJsonObject JsonDbPartition::makeError(int code, const QString &message) +{ + QJsonObject map; + setError(map, code, message); + return map; +} + +QJsonObject JsonDbPartition::makeResponse(const QJsonObject &resultmap, const QJsonObject &errormap, bool silent) +{ + QJsonObject map; + if (jsondbSettings->verbose() && !silent && !errormap.isEmpty()) + qCritical() << errormap; + + if (!resultmap.isEmpty()) + map.insert( JsonDbString::kResultStr, resultmap); + else + map.insert( JsonDbString::kResultStr, QJsonValue()); + + if (!errormap.isEmpty()) + map.insert( JsonDbString::kErrorStr, errormap ); + else + map.insert( JsonDbString::kErrorStr, QJsonValue()); + return map; +} + +QJsonObject JsonDbPartition::makeErrorResponse(QJsonObject &resultmap, int code, const QString &message, bool silent) +{ + QJsonObject errormap; + setError(errormap, code, message); + return makeResponse(resultmap, errormap, silent); +} + +bool JsonDbPartition::responseIsError(const QJsonObject &responseMap) +{ + return responseMap.contains(JsonDbString::kErrorStr) + && responseMap.value(JsonDbString::kErrorStr).isObject(); +} + +bool WithTransaction::addObjectTable(JsonDbObjectTable *table) +{ + if (!mPartition) + return false; + if (!mPartition->mTableTransactions.contains(table)) { + bool ok = table->begin(); + mPartition->mTableTransactions.append(table); + return ok; + } + return true; +} + +void JsonDbPartition::initSchemas() +{ + if (jsondbSettings->verbose()) + qDebug() << "initSchemas"; + { + JsonDbObjectList schemas = getObjects(JsonDbString::kTypeStr, JsonDbString::kSchemaTypeStr, + QString()).data; + for (int i = 0; i < schemas.size(); ++i) { + JsonDbObject schemaObject = schemas.at(i); + QString schemaName = schemaObject.value("name").toString(); + QJsonObject schema = schemaObject.value("schema").toObject(); + setSchema(schemaName, schema); + } + } + + foreach (const QString &schemaName, (QStringList() << JsonDbString::kNotificationTypeStr << JsonDbString::kViewTypeStr + << "Capability" << JsonDbString::kIndexTypeStr)) { + if (!mSchemas.contains(schemaName)) { + QFile schemaFile(QString(":schema/%1.json").arg(schemaName)); + schemaFile.open(QIODevice::ReadOnly); + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(schemaFile.readAll(), &error); + if (doc.isNull()) { + qWarning() << "Parsing " << schemaName << " schema" << error.error; + return; + } + QJsonObject schema = doc.object(); + JsonDbObject schemaObject; + schemaObject.insert(JsonDbString::kTypeStr, JsonDbString::kSchemaTypeStr); + schemaObject.insert("name", schemaName); + schemaObject.insert("schema", schema); + updateObject(mDefaultOwner, schemaObject, ForcedWrite); + } + } + { + 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")); + updateObject(mDefaultOwner, nameIndex, ForcedWrite); + + const QString capabilityName("RootCapability"); + QFile capabilityFile(QString(":schema/%1.json").arg(capabilityName)); + capabilityFile.open(QIODevice::ReadOnly); + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(capabilityFile.readAll(), &error); + if (doc.isNull()) { + qWarning() << "Parsing " << capabilityName << " capability" << error.error; + return; + } + JsonDbObject capability = doc.object(); + QString name = capability.value("name").toString(); + GetObjectsResult getObjectResponse = getObjects("capabilityName", name, "Capability"); + int count = getObjectResponse.data.size(); + if (!count) { + if (jsondbSettings->verbose()) + qDebug() << "Creating capability" << capability; + updateObject(mDefaultOwner, capability); + } else { + JsonDbObject currentCapability = getObjectResponse.data.at(0); + if (currentCapability.value("accessRules") != capability.value("accessRules")) + updateObject(mDefaultOwner, capability); + } + } +} + +#include "moc_jsondbpartition.cpp" + +QT_END_NAMESPACE_JSONDB_PARTITION diff --git a/src/partition/jsondbpartition.h b/src/partition/jsondbpartition.h new file mode 100644 index 00000000..eae6f16d --- /dev/null +++ b/src/partition/jsondbpartition.h @@ -0,0 +1,288 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef JSONDB_PARTITION_H +#define JSONDB_PARTITION_H + +#include <QStringList> +#include <QRegExp> +#include <QSet> +#include <QTimer> +#include <QVector> +#include <QPointer> + +#include "jsondberrors.h" +#include "jsondbobjectkey.h" +#include "jsondbnotification.h" +#include "jsondbowner.h" +#include "jsondbpartitionglobal.h" +#include "jsondbstat.h" +#include "jsondbschemamanager_p.h" + +QT_BEGIN_HEADER + +class TestJsonDb; + +QT_BEGIN_NAMESPACE_JSONDB_PARTITION + +class JsonDbBtree; +class JsonDbOwner; +class JsonDbObjectTable; +class JsonDbIndex; +class JsonDbIndexQuery; +class JsonDbView; + +struct JsonDbUpdate { + JsonDbUpdate(const JsonDbObject &oldObj, const JsonDbObject &newObj, JsonDbNotification::Action act) : + oldObject(oldObj), newObject(newObj), action(act) { } + JsonDbUpdate() : action(JsonDbNotification::None) {} + JsonDbObject oldObject; + JsonDbObject newObject; + JsonDbNotification::Action action; +}; + +typedef QList<JsonDbUpdate> JsonDbUpdateList; + +struct JsonDbWriteResult { + JsonDbWriteResult() : state(0), code(JsonDbError::NoError) { } + JsonDbObjectList objectsWritten; + quint32 state; + JsonDbError::ErrorCode code; + QString message; +}; + +class Q_JSONDB_PARTITION_EXPORT JsonDbPartition : public QObject +{ + Q_OBJECT +public: + + enum WriteMode { + OptimisticWrite, // write must not introduce a conflict + ForcedWrite, // accept write as is (almost no matter what) + ReplicatedWrite, // master/master replication, may create obj._meta.conflicts + ViewObject // internal for view object + }; + + JsonDbPartition(const QString &filename, const QString &name, JsonDbOwner *owner, QObject *parent = 0); + ~JsonDbPartition(); + QString filename() const { return mFilename; } + bool open(); + bool close(); + bool clear(); + + bool beginTransaction(); + bool commitTransaction(quint32 stateNumber = 0); + bool abortTransaction(); + + void initIndexes(); + void flushCaches(); + bool addIndex(const QString &indexName, + const QString &propertyName, + const QString &propertyType = QString("string"), + 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 removeIndex(const QString &indexName, const QString &objectType = QString()); + + bool checkQuota(const JsonDbOwner *owner, int size) const; + bool addToQuota(const JsonDbOwner *owner, int size); + + JsonDbQueryResult queryObjects(const JsonDbOwner *owner, const JsonDbQuery *query, int limit=-1, int offset=0); + JsonDbWriteResult updateObjects(const JsonDbOwner *owner, const JsonDbObjectList &objects, WriteMode mode = OptimisticWrite, JsonDbUpdateList *changeList = 0); + JsonDbWriteResult updateObject(const JsonDbOwner *owner, const JsonDbObject &object, WriteMode mode = OptimisticWrite, JsonDbUpdateList *changeList = 0); + + QJsonObject flush(); + + JsonDbView *addView(const QString &viewType); + void removeView(const QString &viewType); + JsonDbObjectTable *mainObjectTable() const { return mObjectTable; } + JsonDbObjectTable *findObjectTable(const QString &objectType) const; + JsonDbView *findView(const QString &objectType) const; + + bool getObject(const QString &uuid, JsonDbObject &object, const QString &objectType = QString()) const; + bool getObject(const ObjectKey & objectKey, JsonDbObject &object, const QString &objectType = QString()) const; + + GetObjectsResult getObjects(const QString &keyName, const QJsonValue &key, const QString &type = QString(), + bool updateViews = true); + + QJsonObject changesSince(quint32 stateNumber, const QSet<QString> &limitTypes = QSet<QString>()); + + inline QString name() const { return mPartitionName; } + inline void setName(const QString &name) { mPartitionName = name; } + inline JsonDbOwner *defaultOwner() { return mDefaultOwner; } + + void checkIndex(const QString &propertyName); + bool compact(); + JsonDbStat stat() const; + + QHash<QString, qint64> fileSizes() const; + + // FIXME: copied from JsonDb class. + // This is the protocol leaking into the lower layers and should be removed + static void setError(QJsonObject &map, int code, const QString &message); + static QJsonObject makeError(int code, const QString &message); + static QJsonObject makeResponse(const QJsonObject &resultmap, const QJsonObject &errormap, bool silent = false); + static QJsonObject makeErrorResponse(QJsonObject &resultmap, int code, const QString &message, bool silent = false); + static bool responseIsError(const QJsonObject &responseMap); + +public Q_SLOTS: + void updateView(const QString &objectType, quint32 stateNumber=0); + +Q_SIGNALS: + void objectsUpdated(const JsonDbUpdateList &objects); + +protected: + void initSchemas(); + + void timerEvent(QTimerEvent *event); + + bool checkStateConsistency(); + void checkIndexConsistency(JsonDbObjectTable *table, JsonDbIndex *index); + + JsonDbIndexQuery *compileIndexQuery(const JsonDbOwner *owner, const JsonDbQuery *query); + void compileOrQueryTerm(JsonDbIndexQuery *indexQuery, const QueryTerm &queryTerm); + + void doIndexQuery(const JsonDbOwner *owner, JsonDbObjectList &results, int &limit, int &offset, + JsonDbIndexQuery *indexQuery); + + static void sortValues(const JsonDbQuery *query, JsonDbObjectList &results, JsonDbObjectList &joinedResults); + + bool checkCanAddSchema(const JsonDbObject &schema, const JsonDbObject &oldSchema, QString &errorMsg); + bool checkCanRemoveSchema(const JsonDbObject &schema, QString &errorMsg); + bool validateSchema(const QString &schemaName, const JsonDbObject &object, QString &errorMsg); + 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; + + QString mPartitionName; + QString mFilename; + int mTransactionDepth; + bool mTransactionOk; + QHash<QString,QPointer<JsonDbView> > mViews; + QSet<QString> mViewTypes; + JsonDbSchemaManager mSchemas; + QRegExp mWildCardPrefixRegExp; + int mMainSyncTimerId; + int mIndexSyncTimerId; + int mMainSyncInterval; + int mIndexSyncInterval; + JsonDbOwner *mDefaultOwner; + + friend class JsonDbIndexQuery; + friend class JsonDbObjectTable; + friend class JsonDbMapDefinition; + friend class WithTransaction; + friend class ::TestPartition; + friend class ::TestJsonDb; +}; + +class WithTransaction { +public: + WithTransaction(JsonDbPartition *partition=0, QString name=QString()) + : mPartition(0) + { + Q_UNUSED(name) + if (partition) + setPartition(partition); + } + + ~WithTransaction() + { + if (mPartition) + mPartition->commitTransaction(); + } + + void setPartition(JsonDbPartition *partition) + { + Q_ASSERT(!mPartition); + mPartition = partition; + if (!mPartition->beginTransaction()) + mPartition = 0; + } + bool hasBegin() { return mPartition; } + bool addObjectTable(JsonDbObjectTable *table); + + void abort() + { + if (mPartition) + mPartition->abortTransaction(); + mPartition = 0; + } + + void commit(quint32 stateNumber = 0) + { + if (mPartition) + mPartition->commitTransaction(stateNumber); + mPartition = 0; + } + +private: + JsonDbPartition *mPartition; +}; + +QJsonValue makeFieldValue(const QJsonValue &value, const QString &type); +Q_JSONDB_PARTITION_EXPORT QByteArray makeForwardKey(const QJsonValue &fieldValue, const ObjectKey &objectKey); +Q_JSONDB_PARTITION_EXPORT int forwardKeyCmp(const QByteArray &ab, const QByteArray &bb); +void forwardKeySplit(const QByteArray &forwardKey, QJsonValue &fieldValue); +void forwardKeySplit(const QByteArray &forwardKey, QJsonValue &fieldValue, ObjectKey &objectKey); +QByteArray makeForwardValue(const ObjectKey &); +void forwardValueSplit(const QByteArray &forwardValue, ObjectKey &objectKey); + +QDebug &operator<<(QDebug &, const ObjectKey &); + +QT_END_NAMESPACE_JSONDB_PARTITION + +QT_END_HEADER + +#endif // JSONDB_PARTITION_H diff --git a/src/partition/jsondbpartitionglobal.h b/src/partition/jsondbpartitionglobal.h new file mode 100644 index 00000000..9b88409b --- /dev/null +++ b/src/partition/jsondbpartitionglobal.h @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef JSONDB_PARTITION_GLOBAL_H +#define JSONDB_PARTITION_GLOBAL_H + +#include "QtCore/qglobal.h" + +#if defined(QT_JSONDB_PARTITION_LIB) +# define Q_JSONDB_PARTITION_EXPORT Q_DECL_EXPORT +#else +# define Q_JSONDB_PARTITION_EXPORT Q_DECL_IMPORT +#endif + +#if defined(QT_NAMESPACE) +# define QT_BEGIN_NAMESPACE_JSONDB_PARTITION namespace QT_NAMESPACE { namespace QtJsonDb { namespace Partition { +# define QT_END_NAMESPACE_JSONDB_PARTITION } } } +# define QT_USE_NAMESPACE_JSONDB_PARTITION using namespace QT_NAMESPACE::QtJsonDb::Partition; +# define QT_PREPEND_NAMESPACE_JSONDB_PARTITION(name) ::QT_NAMESPACE::QtJsonDb::Partition::name +#else +# define QT_BEGIN_NAMESPACE_JSONDB_PARTITION namespace QtJsonDb { namespace Partition { +# define QT_END_NAMESPACE_JSONDB_PARTITION } } +# define QT_USE_NAMESPACE_JSONDB_PARTITION using namespace QtJsonDb::Partition; +# define QT_PREPEND_NAMESPACE_JSONDB_PARTITION(name) ::QtJsonDb::Partition::name +#endif + +// a workaround for moc - if there is a header file that doesn't use jsondb +// namespace, we still force moc to do "using namespace" but the namespace have to +// be defined, so let's define an empty namespace here +QT_BEGIN_NAMESPACE_JSONDB_PARTITION +QT_END_NAMESPACE_JSONDB_PARTITION + +#endif // JSONDB_PARTITION_GLOBAL_H diff --git a/src/partition/jsondbproxy.cpp b/src/partition/jsondbproxy.cpp new file mode 100644 index 00000000..d6040717 --- /dev/null +++ b/src/partition/jsondbproxy.cpp @@ -0,0 +1,145 @@ +/**************************************************************************** +** +** 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 "jsondbproxy.h" +#include "jsondbstrings.h" +#include "jsondbobject.h" +#include "jsondbsettings.h" + +#include <QDebug> +#include <QJSEngine> + +QT_BEGIN_NAMESPACE_JSONDB_PARTITION + +JsonDbMapProxy::JsonDbMapProxy(const JsonDbOwner *owner, JsonDbPartition *partition, QObject *parent) + : QObject(parent) + , mOwner(owner) + , mPartition(partition) +{ +} +JsonDbMapProxy::~JsonDbMapProxy() +{ +} + +void JsonDbMapProxy::emitViewObject(const QString &key, const QJSValue &v) +{ + QJSValue object = v.engine()->newObject(); + object.setProperty("key", key); + object.setProperty("value", v); + emit viewObjectEmitted(object); +} + +void JsonDbMapProxy::lookup(const QString &key, const QJSValue &value, const QJSValue &context) +{ + QJSValue query = value.engine()->newObject(); + query.setProperty("index", key); + query.setProperty("value", value); + + emit lookupRequested(query, context); +} + +void JsonDbMapProxy::lookupWithType(const QString &key, const QJSValue &value, const QJSValue &objectType, const QJSValue &context) +{ + QJSValue query = value.engine()->newObject(); + query.setProperty("index", key); + query.setProperty("value", value); + query.setProperty("objectType", objectType); + emit lookupRequested(query, context); +} + +JsonDbJoinProxy::JsonDbJoinProxy(const JsonDbOwner *owner, JsonDbPartition *partition, QObject *parent) + : QObject(parent) + , mOwner(owner) + , mPartition(partition) +{ +} +JsonDbJoinProxy::~JsonDbJoinProxy() +{ +} + +void JsonDbJoinProxy::create(const QJSValue &v) +{ + emit viewObjectEmitted(v); +} + +void JsonDbJoinProxy::lookup(const QJSValue &spec, const QJSValue &context) +{ + emit lookupRequested(spec, context); +} + +QString JsonDbJoinProxy::createUuidFromString(const QString &id) +{ + JsonDbObject o; + o.insert(QLatin1String("_id"), id); + o.generateUuid(); + return o.value(JsonDbString::kUuidStr).toString(); +} + +Console::Console() +{ +} + +void Console::log(const QString &s) +{ + if (jsondbSettings->debug()) + qDebug() << s; +} +void Console::debug(const QString &s) +{ + if (jsondbSettings->debug()) + qDebug() << s; +} +void Console::info(const QString &s) +{ + qDebug() << s; +} +void Console::warn(const QString &s) +{ + qWarning() << s; +} +void Console::error(const QString &s) +{ + qCritical() << s; +} + +#include "moc_jsondbproxy.cpp" + +QT_END_NAMESPACE_JSONDB_PARTITION diff --git a/src/partition/jsondbproxy.h b/src/partition/jsondbproxy.h new file mode 100644 index 00000000..886a6ba1 --- /dev/null +++ b/src/partition/jsondbproxy.h @@ -0,0 +1,114 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef JSONDB_PROXY_H +#define JSONDB_PROXY_H + +#include <QObject> +#include <QMultiMap> +#include <QJSValue> + +#include "jsondbpartitionglobal.h" + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE_JSONDB_PARTITION + +class JsonDbOwner; +class JsonDbPartition; + +class Q_JSONDB_PARTITION_EXPORT JsonDbMapProxy : public QObject { + Q_OBJECT +public: + JsonDbMapProxy(const JsonDbOwner *owner, JsonDbPartition *partition, QObject *parent=0); + ~JsonDbMapProxy(); + + Q_SCRIPTABLE void emitViewObject(const QString &key, const QJSValue &value ); + Q_SCRIPTABLE void lookup(const QString &key, const QJSValue &value, const QJSValue &context ); + Q_SCRIPTABLE void lookupWithType(const QString &key, const QJSValue &value, const QJSValue &objectType, const QJSValue &context); + + void setOwner(const JsonDbOwner *owner) { mOwner = owner; } + + signals: + void viewObjectEmitted(const QJSValue &); + // to be removed when all map/lookup are converted to join/lookup + void lookupRequested(const QJSValue &, const QJSValue &); +private: + const JsonDbOwner *mOwner; + JsonDbPartition *mPartition; +}; + +class Q_JSONDB_PARTITION_EXPORT JsonDbJoinProxy : public QObject { + Q_OBJECT +public: + JsonDbJoinProxy( const JsonDbOwner *owner, JsonDbPartition *partition, QObject *parent=0 ); + ~JsonDbJoinProxy(); + + Q_SCRIPTABLE void create(const QJSValue &value ); + Q_SCRIPTABLE void lookup(const QJSValue &spec, const QJSValue &context ); + Q_SCRIPTABLE QString createUuidFromString(const QString &id); + + void setOwner(const JsonDbOwner *owner) { mOwner = owner; } + + signals: + void viewObjectEmitted(const QJSValue &); + void lookupRequested(const QJSValue &, const QJSValue &); +private: + const JsonDbOwner *mOwner; + JsonDbPartition *mPartition; +}; + +class Console : public QObject { + Q_OBJECT +public: + Console(); + Q_SCRIPTABLE void log(const QString &string); + Q_SCRIPTABLE void debug(const QString &string); + Q_SCRIPTABLE void info(const QString &string); + Q_SCRIPTABLE void warn(const QString &string); + Q_SCRIPTABLE void error(const QString &string); +}; + +QT_END_NAMESPACE_JSONDB_PARTITION + +QT_END_HEADER + +#endif // JSONDB_PROXY_H diff --git a/src/partition/jsondbquery.cpp b/src/partition/jsondbquery.cpp new file mode 100644 index 00000000..393ec1f4 --- /dev/null +++ b/src/partition/jsondbquery.cpp @@ -0,0 +1,688 @@ +/**************************************************************************** +** +** 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 <QFile> +#include <QJsonDocument> +#include <QStack> +#include <QString> + +#include "jsondbstrings.h" +#include "jsondbindexquery.h" +#include "jsondbpartition.h" +#include "jsondbquery.h" +#include "jsondbsettings.h" + +QT_BEGIN_NAMESPACE_JSONDB_PARTITION + +const char* JsonDbQueryTokenizer::sTokens[] = { +"[", "]", "{", "}", "/", "?", ",", ":", "|", "\\" +//operators are ordered by precedence +, "!=", "<=", ">=", "=~", "->", "=", ">", "<" +, ""//end of the token list +}; + +JsonDbQueryTokenizer::JsonDbQueryTokenizer(QString input) + : mInput(input), mPos(0) +{ +} + +QString JsonDbQueryTokenizer::pop() +{ + QString token; + if (!mNextToken.isEmpty()) { + token = mNextToken; + mNextToken.clear(); + } else { + token = getNextToken(); + } + return token; +} + +QString JsonDbQueryTokenizer::popIdentifier() +{ + QString identifier = pop(); + if (identifier.startsWith('\"') + && identifier.endsWith('\"')) + identifier = identifier.mid(1, identifier.size()-2); + return identifier; +} + +QString JsonDbQueryTokenizer::peek() +{ + if (mNextToken.isEmpty()) { + mNextToken = getNextToken(); + } + return mNextToken; +} + +QString JsonDbQueryTokenizer::getNextToken() +{ + QString result; + + while (mPos < mInput.size()) { + QChar c = mInput[mPos++]; + if (c == '"') { + // match string + result.append(c); + bool escaped = false; + QChar sc; + int size = mInput.size(); + int i; + //qDebug() << "start of string"; + for (i = mPos; (i < size); i++) { + sc = mInput[i]; + //qDebug() << i << sc << escaped; + if (!escaped && (sc == '"')) + break; + escaped = (sc == '\\'); + } + //qDebug() << "end" << i << sc << escaped; + //qDebug() << mInput.mid(mPos, i-mPos+1); + if ((i < size) && (sc == '"')) { + //qDebug() << mPos << i-mPos << "string is" << mInput.mid(mPos, i-mPos); + result.append(mInput.mid(mPos, i-mPos+1)); + mPos = i+1; + } else { + mPos = i; + result = QString(); + } + return result; + } else if (c.isSpace()) { + if (result.size()) + return result; + else + continue; + } else if (result.size() && mPos+1 < mInput.size()) { + //index expression?[n],[*] + if (c == '[' && mInput[mPos+1] == ']') { + result.append(mInput.mid(mPos-1,3)); + mPos += 2; + continue; + } + } + //operators + int i = 0; + while (sTokens[i][0] != 0) { + if (mInput.midRef(mPos - 1,2).startsWith(sTokens[i])) { + if (!result.isEmpty()) { + mPos --; + return result; + } + result.append(sTokens[i]); + mPos += strlen(sTokens[i]) - 1; + return result; + } + i++; + } + result.append(mInput[mPos-1]); + }//while + return QString(); +} + +QJsonValue QueryTerm::value() const +{ + if (mVariable.size()) + return mQuery->binding(mVariable); + else + return mValue; +} + +JsonDbQuery::JsonDbQuery(const QList<OrQueryTerm> &qt, const QList<OrderTerm> &ot) : + queryTerms(qt) + , orderTerms(ot) +{ +} + +JsonDbQuery::~JsonDbQuery() +{ + queryTerms.clear(); + orderTerms.clear(); +} + +QJsonValue JsonDbQuery::parseJsonLiteral(const QString &json, QueryTerm *term, const QJsonObject &bindings, bool *ok) +{ + const ushort trueLiteral[] = {'t','r','u','e', 0}; + const ushort falseLiteral[] = {'f','a','l','s','e', 0}; + const ushort *literal = json.utf16(); + Q_ASSERT(ok != NULL); + *ok = true; + switch (literal[0]) { + case '"': + term->setValue(json.mid(1, json.size()-2)); + break; + case 't': + // we will interpret "true0something" as true is it a real problem ? + for (int i = 1; i < 5 /* 'true0' length */; ++i) { + if (trueLiteral[i] != literal[i]) { + *ok = false; + return term->value(); + } + } + term->setValue(true); + break; + case 'f': + // we will interpret "false0something" as false is it a real problem ? + for (int i = 1; i < 6 /* 'false0' length */; ++i) { + if (falseLiteral[i] != literal[i]) { + *ok = false; + return term->value(); + } + } + term->setValue(false); + break; + case '%': + { + const QString name = json.mid(1); + if (bindings.contains(name)) + term->setValue(bindings.value(name)); + else + term->setVariable(name); + break; + } + case 0: + // This can happen if json.length() == 0 + *ok = false; + return term->value(); + default: + int result = json.toInt(ok); + if (*ok) { + term->setValue(result); + } else { + // bad luck, it can be only a double + term->setValue(json.toDouble(ok)); + } + } + return term->value(); +} + +JsonDbQuery *JsonDbQuery::parse(const QString &query, const QJsonObject &bindings) +{ + JsonDbQuery *parsedQuery = new JsonDbQuery; + parsedQuery->query = query; + + if (!query.startsWith('[')) + return parsedQuery; + + bool parseError = false; + JsonDbQueryTokenizer tokenizer(query); + QString token; + while (!parseError + && !(token = tokenizer.pop()).isEmpty()) { + if (token != "[") { + qCritical() << "unexpected token" << token; + break; + } + token = tokenizer.pop(); + if (token == "?") { + OrQueryTerm oqt; + do { + QString fieldSpec = tokenizer.popIdentifier(); + if (fieldSpec == "|") + fieldSpec = tokenizer.popIdentifier(); + + QString opOrJoin = tokenizer.pop(); + QString op; + QStringList joinFields; + QString joinField; + while (opOrJoin == "->") { + joinFields.append(fieldSpec); + fieldSpec = tokenizer.popIdentifier(); + opOrJoin = tokenizer.pop(); + } + if (joinFields.size()) + joinField = joinFields.join("->"); + op = opOrJoin; + + + QueryTerm term(parsedQuery); + if (!joinField.isEmpty()) + term.setJoinField(joinField); + if (fieldSpec.startsWith(QChar('%'))) { + const QString name = fieldSpec.mid(1); + if (bindings.contains(name)) { + QJsonValue val = bindings.value(name); + parsedQuery->bind(name, val); + } + term.setPropertyVariable(name); + } + else + term.setPropertyName(fieldSpec); + term.setOp(op); + if (op == "=~") { + QString tvs = tokenizer.pop(); + int sepPos = 1; // assuming it's a literal "/regexp/modifiers" + if (tvs.startsWith(QChar('%'))) { + const QString name = tvs.mid(1); + if (bindings.contains(name)) { + tvs = bindings.value(name).toString(); + sepPos = 0; + } + } else if (!tvs.startsWith("\"")) { + parsedQuery->queryExplanation.append(QString("Failed to parse query regular expression '%1' in query '%2' %3 op %4") + .arg(tvs) + .arg(parsedQuery->query) + .arg(fieldSpec) + .arg(op)); + parseError = true; + break; + } + QChar sep = tvs[sepPos]; + int eor = sepPos; + do { + eor = tvs.indexOf(sep, eor+1); // end of regexp; + //qDebug() << "tvs" << tvs << "eor" << eor << "tvs[eor-1]" << ((eor > 0) ? tvs[eor-1] : QChar('*')); + if (eor <= sepPos) { + parseError = true; + break; + } + } while ((eor > 0) && (tvs[eor-1] == '\\')); + QString modifiers = tvs.mid(eor+1,tvs.size()-eor-2*sepPos); + if (jsondbSettings->debug()) { + qDebug() << "modifiers" << modifiers; + qDebug() << "regexp" << tvs.mid(sepPos + 1, eor-sepPos-1); + } + if (modifiers.contains('w')) + term.regExp().setPatternSyntax(QRegExp::Wildcard); + if (modifiers.contains('i')) + term.regExp().setCaseSensitivity(Qt::CaseInsensitive); + //qDebug() << "pattern" << tvs.mid(2, eor-2); + term.regExp().setPattern(tvs.mid(sepPos + 1, eor-sepPos-1)); + } 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;; + if (value == "[") { + QJsonArray values; + while (1) { + value = tokenizer.pop(); + if (value == "]") + break; + parseJsonLiteral(value, &term, bindings, &ok); + if (!ok) + break; + values.append(term.value()); + if (tokenizer.peek() == ",") + tokenizer.pop(); + } + term.setValue(values); + } 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; + } + } + + oqt.addTerm(term); + } while (tokenizer.peek() != "]"); + parsedQuery->queryTerms.append(oqt); + } else if (token == "=") { + QString curlyBraceToken = tokenizer.pop(); + if (curlyBraceToken != "{") { + parsedQuery->queryExplanation.append(QString("Parse error: expecting '{' but got '%1'") + .arg(curlyBraceToken)); + parseError = true; + break; + } + QString nextToken; + while (!(nextToken = tokenizer.popIdentifier()).isEmpty()) { + if (nextToken == "}") { + break; + } else { + + //qDebug() << "isMapObject" << nextToken << tokenizer.peek(); + parsedQuery->mapKeyList.append(nextToken); + QString colon = tokenizer.pop(); + if (colon != ":") { + parsedQuery->queryExplanation.append(QString("Parse error: expecting ':' but got '%1'").arg(colon)); + parseError = true; + break; + } + nextToken = tokenizer.popIdentifier(); + + while (tokenizer.peek() == "->") { + QString op = tokenizer.pop(); + nextToken.append(op); + nextToken.append(tokenizer.popIdentifier()); + } + parsedQuery->mapExpressionList.append(nextToken); + QString maybeComma = tokenizer.pop(); + if (maybeComma == "}") { + tokenizer.push(maybeComma); + continue; + } else if (maybeComma != ",") { + parsedQuery->queryExplanation.append(QString("Parse error: expecting ',', or '}' but got '%1'") + .arg(maybeComma)); + parseError = true; + break; + } + } + } + } else if ((token == "/") || (token == "\\") || (token == ">") || (token == "<")) { + QString ordering = token; + OrderTerm term; + term.propertyName = tokenizer.popIdentifier(); + term.ascending = ((ordering == "/") || (ordering == ">")); + parsedQuery->orderTerms.append(term); + } else if (token == "count") { + parsedQuery->mAggregateOperation = "count"; + } else if (token == "*") { + // match all objects + } else { + qCritical() << QString("Parse error: expecting '?', '/', '\\', or 'count' but got '%1'").arg(token); + parseError = true; + break; + } + QString closeBracket = tokenizer.pop(); + if (closeBracket != "]") { + qCritical() << QString("Parse error: expecting ']' but got '%1'").arg(closeBracket); + parseError = true; + break; + } + } + + if (parseError) { + QStringList explanation = parsedQuery->queryExplanation; + delete parsedQuery; + parsedQuery = new JsonDbQuery; + parsedQuery->queryExplanation = explanation; + qCritical() << "Parser error: query" << query << explanation; + return parsedQuery; + } + + foreach (const OrQueryTerm &oqt, parsedQuery->queryTerms) { + foreach (const QueryTerm &term, oqt.terms()) { + if (term.propertyName() == JsonDbString::kTypeStr) { + if (term.op() == "=") { + parsedQuery->mMatchedTypes.clear(); + parsedQuery->mMatchedTypes.insert(term.value().toString()); + } else if (term.op() == "!=") { + parsedQuery->mMatchedTypes.insert(term.value().toString()); + } + } + } + } + + if (!parsedQuery->queryTerms.size() && !parsedQuery->orderTerms.size()) { + // match everything -- sort on type + OrderTerm term; + term.propertyName = JsonDbString::kTypeStr; + term.ascending = true; + parsedQuery->orderTerms.append(term); + } + + //qDebug() << "queryTerms.size()" << parsedQuery->queryTerms.size(); + //qDebug() << "orderTerms.size()" << parsedQuery->orderTerms.size(); + return parsedQuery; +} + +bool JsonDbQuery::match(const JsonDbObject &object, QHash<QString, JsonDbObject> *objectCache, JsonDbPartition *partition) const +{ + for (int i = 0; i < queryTerms.size(); i++) { + const OrQueryTerm &orQueryTerm = queryTerms[i]; + bool matches = false; + foreach (const QueryTerm &term, orQueryTerm.terms()) { + const QString &joinPropertyName = term.joinField(); + const QString &op = term.op(); + const QJsonValue &termValue = term.value(); + + QJsonValue objectFieldValue; + if (!joinPropertyName.isEmpty()) { + JsonDbObject joinedObject = object; + const QVector<QStringList> &joinPaths = term.joinPaths(); + for (int j = 0; j < joinPaths.size(); j++) { + if (!joinPaths[j].size()) { + if (jsondbSettings->debug()) + qDebug() << term.joinField() << term.joinPaths(); + } + QString uuidValue = joinedObject.propertyLookup(joinPaths[j]).toString(); + if (objectCache && objectCache->contains(uuidValue)) + joinedObject = objectCache->value(uuidValue); + else if (partition) { + ObjectKey objectKey(uuidValue); + partition->getObject(objectKey, joinedObject); + if (objectCache) objectCache->insert(uuidValue, joinedObject); + } + } + objectFieldValue = joinedObject.propertyLookup(term.fieldPath()); + } else { + if (term.propertyName().isEmpty()) + objectFieldValue = binding(term.propertyVariable()); + else + objectFieldValue = object.propertyLookup(term.fieldPath()); + } + if ((op == "=") || (op == "==")) { + if (objectFieldValue == termValue) + matches = true; + } else if ((op == "<>") || (op == "!=")) { + if (objectFieldValue != termValue) + matches = true; + } else if (op == "=~") { + if (jsondbSettings->debug()) + qDebug() << objectFieldValue.toString() << term.regExpConst().exactMatch(objectFieldValue.toString()); + if (term.regExpConst().exactMatch(objectFieldValue.toString())) + matches = true; + } else if (op == "<=") { + matches = JsonDbIndexQuery::lessThan(objectFieldValue, termValue) || (objectFieldValue == termValue); + } else if (op == "<") { + matches = JsonDbIndexQuery::lessThan(objectFieldValue, termValue); + } else if (op == ">=") { + matches = JsonDbIndexQuery::greaterThan(objectFieldValue, termValue) || (objectFieldValue == termValue); + } else if (op == ">") { + matches = JsonDbIndexQuery::greaterThan(objectFieldValue, termValue); + } else if (op == "exists") { + if (objectFieldValue.type() != QJsonValue::Undefined) + matches = true; + } else if (op == "notExists") { + if (objectFieldValue.type() == QJsonValue::Undefined) + matches = true; + } else if (op == "in") { + if (0) qDebug() << __FUNCTION__ << __LINE__ << objectFieldValue << "in" << termValue + << termValue.toArray().contains(objectFieldValue); + if (termValue.toArray().contains(objectFieldValue)) + matches = true; + } else if (op == "notIn") { + if (0) qDebug() << __FUNCTION__ << __LINE__ << objectFieldValue << "notIn" << termValue + << !termValue.toArray().contains(objectFieldValue); + if (!termValue.toArray().contains(objectFieldValue)) + matches = true; + } else if (op == "contains") { + if (0) qDebug() << __FUNCTION__ << __LINE__ << objectFieldValue << "contains" << termValue + << objectFieldValue.toArray().contains(termValue); + if (objectFieldValue.toArray().contains(termValue)) + matches = true; + } else if (op == "startsWith") { + if ((objectFieldValue.type() == QJsonValue::String) + && objectFieldValue.toString().startsWith(termValue.toString())) + matches = true; + } else { + qCritical() << "match" << "unhandled term" << term.propertyName() << term.op() << term.value() << term.joinField(); + } + } + if (!matches) + return false; + } + return true; +} + + + +QueryTerm::QueryTerm(const JsonDbQuery *query) + : mQuery(query), mJoinPaths() +{ +} + +QueryTerm::~QueryTerm() +{ + mValue = QJsonValue(); + mJoinPaths.clear(); +} + +OrQueryTerm::OrQueryTerm() +{ +} + +OrQueryTerm::OrQueryTerm(const QueryTerm &term) +{ + mTerms.append(term); +} + +OrQueryTerm::~OrQueryTerm() +{ +} + +QList<QString> OrQueryTerm::propertyNames() const +{ + QList<QString> propertyNames; + foreach (const QueryTerm &term, mTerms) { + QString propertyName = term.propertyName(); + if (!propertyNames.contains(propertyName)) + propertyNames.append(propertyName); + } + 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() +{ +} + +OrderTerm::~OrderTerm() +{ +} + +QVariantMap JsonDbQueryResult::toVariantMap() const +{ + QJsonObject resultmap, errormap; + QJsonArray variantList; + for (int i = 0; i < data.size(); i++) + variantList.append(data.at(i)); + resultmap.insert(JsonDbString::kDataStr, variantList); + resultmap.insert(JsonDbString::kLengthStr, data.size()); + resultmap.insert(JsonDbString::kOffsetStr, offset); + resultmap.insert(JsonDbString::kExplanationStr, explanation); + resultmap.insert(QString("sortKeys"), sortKeys); + if (error.isObject()) + errormap = error.toObject(); + return JsonDbPartition::makeResponse(resultmap, errormap).toVariantMap(); +} + +JsonDbQueryResult JsonDbQueryResult::makeErrorResponse(JsonDbError::ErrorCode code, const QString &message, bool silent) +{ + JsonDbQueryResult result; + QJsonObject errormap; + errormap.insert(JsonDbString::kCodeStr, code); + errormap.insert(JsonDbString::kMessageStr, message); + result.error = errormap; + if (jsondbSettings->verbose() && !silent && !errormap.isEmpty()) + qCritical() << errormap; + return result; +} + +QT_END_NAMESPACE_JSONDB_PARTITION diff --git a/src/partition/jsondbquery.h b/src/partition/jsondbquery.h new file mode 100644 index 00000000..324f9ee1 --- /dev/null +++ b/src/partition/jsondbquery.h @@ -0,0 +1,201 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef JSONDB_QUERY_H +#define JSONDB_QUERY_H + +#include <QDebug> +#include <QObject> +#include <QHash> +#include <QStringList> +#include <QVariant> +#include "jsondberrors.h" + +#include <qjsonarray.h> +#include <qjsonobject.h> +#include <qjsonvalue.h> + +#include "jsondbobject.h" + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE_JSONDB_PARTITION + +class JsonDbPartition; + +class Q_JSONDB_PARTITION_EXPORT JsonDbQueryTokenizer { +public: + JsonDbQueryTokenizer(QString input); + QString pop(); + QString popIdentifier(); + QString peek(); + void push(QString token) { + if (!mNextToken.isEmpty()) + qCritical() << Q_FUNC_INFO << "Cannot push multiple tokens"; + mNextToken = token; + } +protected: + QString getNextToken(); + static const char* sTokens[]; +private: + QString mInput; + int mPos; + QString mNextToken; +}; + +class JsonDbQuery; +class Q_JSONDB_PARTITION_EXPORT QueryTerm { +public: + QueryTerm(const JsonDbQuery *query); + ~QueryTerm(); + QString propertyName() const { return mPropertyName; } + void setPropertyName(QString propertyName) { mPropertyName = propertyName; mFieldPath = propertyName.split('.'); } + const QStringList &fieldPath() const { return mFieldPath; } + + QString op() const { return mOp; } + void setOp(QString op) { mOp = op; } + + QString joinField() const { return mJoinField; } + void setJoinField(QString joinField) { + mJoinField = joinField; + if (!joinField.isEmpty()) { + QStringList joinFields = joinField.split("->"); + mJoinPaths.resize(joinFields.size()); + for (int j = 0; j < joinFields.size(); j++) + mJoinPaths[j] = joinFields[j].split('.'); + } + } + const QVector<QStringList> &joinPaths() const { return mJoinPaths; } + + QString variable() const { return mVariable; } + void setVariable(const QString variable) { mVariable = variable; } + + QString propertyVariable() const { return mPropertyVariable; } + void setPropertyVariable(const QString variable) { mPropertyVariable = variable; } + + QJsonValue value() const; + void setValue(const QJsonValue &v) { mValue = v; } + QRegExp ®Exp() { return mRegExp; } + void setRegExp(const QRegExp ®Exp) { mRegExp = regExp; } + const QRegExp ®ExpConst() const { return mRegExp; } + + private: + const JsonDbQuery *mQuery; + QString mVariable; + QString mPropertyName; + QString mPropertyVariable; + QStringList mFieldPath; + QString mOp; + QString mJoinField; + QVector<QStringList> mJoinPaths; + QJsonValue mValue; + QRegExp mRegExp; +}; + +class OrQueryTerm { +public: + OrQueryTerm(); + OrQueryTerm(const QueryTerm &term); + ~OrQueryTerm(); + 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; +}; + +class Q_JSONDB_PARTITION_EXPORT OrderTerm { +public: + OrderTerm(); + ~OrderTerm(); + bool ascending; + QString propertyName; +}; + +class Q_JSONDB_PARTITION_EXPORT JsonDbQuery { +public: + JsonDbQuery() { } + JsonDbQuery(const QList<OrQueryTerm> &qt, const QList<OrderTerm> &ot); + ~JsonDbQuery(); + QList<OrQueryTerm> queryTerms; + QList<OrderTerm> orderTerms; + QString query; + QStringList mapExpressionList; + QStringList mapKeyList; + QStringList queryExplanation; + QString mAggregateOperation; + + QSet<QString> matchedTypes() const { return mMatchedTypes; } + QJsonValue binding(const QString variable) const { return mBindings.value(variable); } + void bind(QString variable, QJsonValue &binding) { mBindings[variable] = binding; } + bool match(const JsonDbObject &object, QHash<QString, JsonDbObject> *objectCache, JsonDbPartition *partition = 0) const; + + static QJsonValue parseJsonLiteral(const QString &json, QueryTerm *term, const QJsonObject &bindings, bool *ok); + static JsonDbQuery *parse(const QString &query, const QJsonObject &bindings = QJsonObject()); + +private: + QSet<QString> mMatchedTypes; + QMap<QString,QJsonValue> mBindings; + Q_DISABLE_COPY(JsonDbQuery); +}; + +typedef QList<QJsonValue> QJsonValueList; +typedef QList<QJsonObject> QJsonObjectList; +typedef QList<JsonDbObject> JsonDbObjectList; +class Q_JSONDB_PARTITION_EXPORT JsonDbQueryResult { +public: + JsonDbObjectList data; + QJsonValue length; + QJsonValue offset; + QJsonValue explanation; + QJsonValue sortKeys; + QJsonValue state; + QJsonValue error; // { code: int, message: string } + QVariantMap toVariantMap() const; + static JsonDbQueryResult makeErrorResponse(JsonDbError::ErrorCode, const QString&, bool silent=false); +}; + +QT_END_NAMESPACE_JSONDB_PARTITION + +QT_END_HEADER + +#endif // JSONDB_QUERY_H diff --git a/src/partition/jsondbreducedefinition.cpp b/src/partition/jsondbreducedefinition.cpp new file mode 100644 index 00000000..ded9357e --- /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 diff --git a/src/partition/jsondbreducedefinition.h b/src/partition/jsondbreducedefinition.h new file mode 100644 index 00000000..8b063338 --- /dev/null +++ b/src/partition/jsondbreducedefinition.h @@ -0,0 +1,127 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef JSONDB_REDUCE_DEFINITION_H +#define JSONDB_REDUCE_DEFINITION_H + +#include <QJSEngine> +#include <QStringList> + +#include "jsondbpartitionglobal.h" +#include <qjsonarray.h> +#include <qjsonobject.h> +#include <qjsonvalue.h> + +#include <jsondbobject.h> +#include <jsondbpartition.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE_JSONDB_PARTITION + +class JsonDbOwner; +class JsonDbJoinProxy; +class JsonDbMapProxy; +class JsonDbPartition; +class JsonDbObjectTable; + +class JsonDbReduceDefinition : public QObject +{ + Q_OBJECT +public: + JsonDbReduceDefinition(const JsonDbOwner *mOwner, JsonDbPartition *partition, QJsonObject reduceDefinition, QObject *parent = 0); + QString uuid() const { return mUuid; } + QString targetType() const { return mTargetType; } + QString sourceType() const { return mSourceType; } + QString sourceKeyName() const { return mSourceKeyName; } + QString targetKeyName() const { return mTargetKeyName; } + QString targetValueName() const { return mTargetValueName; } + // sourceKeyName split on . + QStringList sourceKeyNameList() const { return mSourceKeyNameList; } + bool isActive() const; + QJsonObject definition() const { return mDefinition; } + const JsonDbOwner *owner() const { return mOwner; } + + static void definitionRemoved(JsonDbPartition *partition, JsonDbObjectTable *table, const QString targetType, const QString &definitionUuid); + void definitionCreated(); + + void initScriptEngine(); + void releaseScriptEngine(); + void initIndexes(); + + void updateObject(JsonDbObject before, JsonDbObject after, JsonDbUpdateList *changeList = 0); + + 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 JsonDbObject &object); + QJsonValue addObject(FunctionNumber fn, const QJsonValue &keyValue, QJsonValue previousResult, JsonDbObject object); + +private: + const JsonDbOwner *mOwner; + JsonDbPartition *mPartition; + QJsonObject mDefinition; + QJSEngine *mScriptEngine; + QVector<QJSValue> mFunctions; + QString mUuid; + QString mTargetType; + JsonDbObjectTable *mTargetTable; + QString mSourceType; + QString mTargetKeyName; + QString mTargetValueName; + QString mSourceKeyName; + // mSourceKeyName split on . + QStringList mSourceKeyNameList; +}; + +QT_END_NAMESPACE_JSONDB_PARTITION + +QT_END_HEADER + +#endif // JSONDB_REDUCE_DEFINITION_H diff --git a/src/partition/jsondbschemamanager_impl_p.h b/src/partition/jsondbschemamanager_impl_p.h new file mode 100644 index 00000000..21d03746 --- /dev/null +++ b/src/partition/jsondbschemamanager_impl_p.h @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef JSONDB_SCHEMA_MANAGER_IMPL_P_H +#define JSONDB_SCHEMA_MANAGER_IMPL_P_H + +#include "jsondbschemamanager_p.h" +#include "schema-validation/object.h" + +QT_BEGIN_NAMESPACE_JSONDB_PARTITION + +bool JsonDbSchemaManager::contains(const QString &name) const +{ + return m_schemas.contains(name); +} + +QJsonObject JsonDbSchemaManager::value(const QString &name) const +{ + return m_schemas.value(name).first; +} + +SchemaValidation::Schema<QJsonObjectTypes> JsonDbSchemaManager::schema(const QString &schemaName, QJsonObjectTypes::Service *service) +{ + QJsonObjectSchemaPair schemaPair = m_schemas.value(schemaName); + ensureCompiled(schemaName, &schemaPair, service); + return schemaPair.second; +} + +QJsonObject JsonDbSchemaManager::take(const QString &name) +{ + return m_schemas.take(name).first; +} + +QJsonObject JsonDbSchemaManager::insert(const QString &name, const QJsonObject &schema) +{ + m_schemas.insert(name, qMakePair(schema, SchemaValidation::Schema<QJsonObjectTypes>())); + return QJsonObject(); +} + +inline QJsonObject JsonDbSchemaManager::ensureCompiled(const QString &schemaName, QJsonObjectSchemaPair *pair, QJsonObjectTypes::Service *callbacks) +{ + SchemaValidation::Schema<QJsonObjectTypes> schema(pair->second); + if (!schema.isValid()) { + // Try to compile schema + QJsonObjectTypes::Object schemaObject(pair->first); + SchemaValidation::Schema<QJsonObjectTypes> compiledSchema(schemaObject, callbacks); + pair->second = compiledSchema; + m_schemas.insert(schemaName, *pair); + return callbacks->error(); + } + return QJsonObject(); +} + +inline QJsonObject JsonDbSchemaManager::validate(const QString &schemaName, JsonDbObject object) +{ + if (!contains(schemaName)) + return QJsonObject(); + + QJsonObjectTypes::Service callbacks(this); + QJsonObjectSchemaPair schemaPair = m_schemas.value(schemaName); + ensureCompiled(schemaName, &schemaPair, &callbacks); + SchemaValidation::Schema<QJsonObjectTypes> schema(schemaPair.second); + QJsonObjectTypes::Value rootObject(QString(), object); + /*bool result = */ schema.check(rootObject, &callbacks); + return callbacks.error(); +} + +QT_END_NAMESPACE_JSONDB_PARTITION + +#endif // JSONDB_SCHEMA_MANAGER_IMPL_P_H diff --git a/src/partition/jsondbschemamanager_p.h b/src/partition/jsondbschemamanager_p.h new file mode 100644 index 00000000..18958c27 --- /dev/null +++ b/src/partition/jsondbschemamanager_p.h @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef JSONDB_SCHEMA_MANAGER_P_H +#define JSONDB_SCHEMA_MANAGER_P_H + +#include "jsondbpartitionglobal.h" + +#include <QtCore/qstring.h> +#include <QtCore/qpair.h> +#include <QtCore/qmap.h> + +#include "schema-validation/object.h" +#include "jsondbobjecttypes_p.h" +#include "jsondbobject.h" + +QT_BEGIN_NAMESPACE_JSONDB_PARTITION + +//FIXME This can have better performance +class JsonDbSchemaManager +{ +public: + inline bool contains(const QString &name) const; + inline QJsonObject value(const QString &name) const; + inline SchemaValidation::Schema<QJsonObjectTypes> schema(const QString &name, QJsonObjectTypes::Service *service); + inline QJsonObject take(const QString &name); + inline QJsonObject insert(const QString &name, const QJsonObject &schema); + + inline QJsonObject validate(const QString &schemaName, JsonDbObject object); + +private: + typedef QPair<QJsonObject, SchemaValidation::Schema<QJsonObjectTypes> > QJsonObjectSchemaPair; + inline QJsonObject ensureCompiled(const QString &schemaName, QJsonObjectSchemaPair *pair, QJsonObjectTypes::Service *service); + + QMap<QString, QJsonObjectSchemaPair> m_schemas; +}; + +QT_END_NAMESPACE_JSONDB_PARTITION + +#endif // JSONDB_SCHEMA_MANAGER_P_H diff --git a/src/partition/jsondbscriptengine.cpp b/src/partition/jsondbscriptengine.cpp new file mode 100644 index 00000000..e4c80967 --- /dev/null +++ b/src/partition/jsondbscriptengine.cpp @@ -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 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 "jsondbobject.h" + +#include <QJSValue> +#include <QJSValueIterator> +#include <QStringBuilder> +#include <QStringList> +#include <QCryptographicHash> + +#include <qjsondocument.h> + +#include "jsondbstrings.h" +#include "jsondbproxy.h" +#include "jsondbscriptengine.h" + +QT_BEGIN_NAMESPACE_JSONDB_PARTITION + +QJsonValue JsonDbScriptEngine::fromJSValue(const QJSValue &v) +{ + if (v.isNull()) + return QJsonValue(QJsonValue::Null); + if (v.isNumber()) + return QJsonValue(v.toNumber()); + if (v.isString()) + return QJsonValue(v.toString()); + if (v.isBool()) + return QJsonValue(v.toBool()); + if (v.isArray()) { + QJsonArray a; + int size = v.property("length").toInt(); + for (int i = 0; i < size; i++) { + a.append(fromJSValue(v.property(i))); + } + return a; + } + if (v.isObject()) { + QJSValueIterator it(v); + QJsonObject o; + while (it.hasNext()) { + it.next(); + QString name = it.name(); + QJSValue value = it.value(); + o.insert(name, fromJSValue(value)); + } + return o; + } + return QJsonValue(QJsonValue::Undefined); +} + +QJSValue JsonDbScriptEngine::toJSValue(const QJsonValue &v, QJSEngine *scriptEngine) +{ + switch (v.type()) { + case QJsonValue::Null: + return QJSValue(QJSValue::NullValue); + case QJsonValue::Undefined: + return QJSValue(QJSValue::UndefinedValue); + case QJsonValue::Double: + return QJSValue(v.toDouble()); + case QJsonValue::String: + return QJSValue(v.toString()); + case QJsonValue::Bool: + return QJSValue(v.toBool()); + case QJsonValue::Array: { + QJSValue jsArray = scriptEngine->newArray(); + QJsonArray array = v.toArray(); + for (int i = 0; i < array.size(); i++) + jsArray.setProperty(i, toJSValue(array.at(i), scriptEngine)); + return jsArray; + } + case QJsonValue::Object: + return toJSValue(v.toObject(), scriptEngine); + } + return QJSValue(QJSValue::UndefinedValue); +} + +QJSValue JsonDbScriptEngine::toJSValue(const QJsonObject &object, QJSEngine *scriptEngine) +{ + QJSValue jsObject = scriptEngine->newObject(); + for (QJsonObject::const_iterator it = object.begin(); it != object.end(); ++it) + jsObject.setProperty(it.key(), toJSValue(it.value(), scriptEngine)); + return jsObject; +} + +static QJSEngine *sScriptEngine; + +QJSEngine *JsonDbScriptEngine::scriptEngine() +{ + if (!sScriptEngine) { + sScriptEngine = new QJSEngine(); + QJSValue globalObject = sScriptEngine->globalObject(); + globalObject.setProperty("console", sScriptEngine->newQObject(new Console())); + } + return sScriptEngine; +} + +QT_END_NAMESPACE_JSONDB_PARTITION diff --git a/src/partition/jsondbscriptengine.h b/src/partition/jsondbscriptengine.h new file mode 100644 index 00000000..663a38fc --- /dev/null +++ b/src/partition/jsondbscriptengine.h @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef JSONDB_SCRIPT_ENGINE_H +#define JSONDB_SCRIPT_ENGINE_H + +#include <QJSEngine> +#include <QUuid> + +#include <qjsonarray.h> +#include <qjsonobject.h> +#include <qjsonvalue.h> + +#include "jsondbpartitionglobal.h" + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE_JSONDB_PARTITION + +class JsonDbScriptEngine : public QObject +{ +public: + JsonDbScriptEngine(); + JsonDbScriptEngine(const QJsonObject &object); + ~JsonDbScriptEngine(); + + static QJsonValue fromJSValue(const QJSValue &v); + static QJSValue toJSValue(const QJsonValue &v, QJSEngine *scriptEngine); + static QJSValue toJSValue(const QJsonObject &object, QJSEngine *mScriptEngine); + static QJSEngine *scriptEngine(); +}; + +QT_END_NAMESPACE_JSONDB_PARTITION + +QT_END_HEADER + +#endif // JSONDB_OBJECT_H diff --git a/src/partition/jsondbsettings.cpp b/src/partition/jsondbsettings.cpp new file mode 100644 index 00000000..44343cea --- /dev/null +++ b/src/partition/jsondbsettings.cpp @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** 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 "jsondbsettings.h" + +#include <QDebug> +#include <QMetaObject> +#include <QMetaProperty> + +QT_BEGIN_NAMESPACE_JSONDB_PARTITION + +Q_GLOBAL_STATIC(JsonDbSettings, staticInstance) + +JsonDbSettings::JsonDbSettings() : + mRejectStaleUpdates(false) + , mDebug(false) + , mVerbose(false) + , mPerformanceLog(false) + , mCacheSize(128) + , mCompactRate(1000) + , mEnforceAccessControl(false) + , mTransactionSize(100) + , mValidateSchemas(false) + , mSyncInterval(5000) + , mIndexSyncInterval(12000) + , mDebugQuery(false) +{ + loadEnvironment(); +} + +void JsonDbSettings::loadEnvironment() +{ + // loop through all the properties, converting from the property + // name to the environment variable name + const QMetaObject *meta = metaObject(); + + for (int i = 0; i < meta->propertyCount(); i++) { + QMetaProperty property(meta->property(i)); + QString propertyName = QString::fromLatin1(property.name()); + QString envVariable = QLatin1String("JSONDB_"); + + for (int j = 0; j < propertyName.count(); j++) { + if (j > 0 && propertyName.at(j).isUpper() && !propertyName.at(j - 1).isUpper()) + envVariable += QString("_%1").arg(propertyName.at(j)); + else + envVariable += propertyName.at(j).toUpper(); + } + + if (qgetenv(envVariable.toLatin1()).size()) { + if (property.type() == QVariant::Bool) + property.write(this, QLatin1String(qgetenv(envVariable.toLatin1())) == QLatin1String("true")); + else if (property.type() == QVariant::Int) + property.write(this, qgetenv(envVariable.toLatin1()).toInt()); + else if (property.type() == QVariant::String) + property.write(this, qgetenv(envVariable.toLatin1())); + else if (property.type() == QVariant::StringList) + property.write(this, QString::fromLatin1(qgetenv(envVariable.toLatin1())).split(':')); + else + qWarning() << "JsonDbSettings: unknown property type" << property.name() << property.type(); + } + } +} + +JsonDbSettings *JsonDbSettings::instance() +{ + return staticInstance(); +} + +#include "moc_jsondbsettings.cpp" + +QT_END_NAMESPACE_JSONDB_PARTITION diff --git a/src/partition/jsondbsettings.h b/src/partition/jsondbsettings.h new file mode 100644 index 00000000..19ee5422 --- /dev/null +++ b/src/partition/jsondbsettings.h @@ -0,0 +1,141 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef JSONDB_SETTINGS_H +#define JSONDB_SETTINGS_H + +#include "jsondbpartitionglobal.h" + +#include <QObject> +#include <QStringList> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE_JSONDB_PARTITION + +#define jsondbSettings JsonDbSettings::instance() + +class Q_JSONDB_PARTITION_EXPORT JsonDbSettings : public QObject +{ + Q_OBJECT + Q_PROPERTY(bool rejectStaleUpdates READ rejectStaleUpdates WRITE setRejectStaleUpdates) + Q_PROPERTY(bool debug READ debug WRITE setDebug) + Q_PROPERTY(bool verbose READ verbose WRITE setVerbose) + Q_PROPERTY(bool performanceLog READ performanceLog WRITE setPerformanceLog) + Q_PROPERTY(int cacheSize READ cacheSize WRITE setCacheSize) + Q_PROPERTY(int compactRate READ compactRate WRITE setCompactRate) + Q_PROPERTY(bool enforceAccessControl READ enforceAccessControl WRITE setEnforceAccessControl) + Q_PROPERTY(int transactionSize READ transactionSize WRITE setTransactionSize) + Q_PROPERTY(bool validateSchemas READ validateSchemas WRITE setValidateSchemas) + Q_PROPERTY(int syncInterval READ syncInterval WRITE setSyncInterval) + Q_PROPERTY(int indexSyncInterval READ indexSyncInterval WRITE setIndexSyncInterval) + Q_PROPERTY(bool debugQuery READ debugQuery WRITE setDebugQuery) + Q_PROPERTY(QStringList configSearchPath READ configSearchPath WRITE setConfigSearchPath) + +public: + static JsonDbSettings *instance(); + + inline void reload() { loadEnvironment(); } + + inline bool rejectStaleUpdates() const { return mRejectStaleUpdates; } + inline void setRejectStaleUpdates(bool reject) { mRejectStaleUpdates = reject; } + + inline bool debug() const { return mDebug; } + inline void setDebug(bool debug) { mDebug = debug; } + + inline bool verbose() const { return mVerbose; } + inline void setVerbose(bool verbose) { mVerbose = verbose; } + + inline bool performanceLog() const { return mPerformanceLog; } + inline void setPerformanceLog(bool performanceLog) { mPerformanceLog = performanceLog; } + + inline int cacheSize() const { return mCacheSize; } + inline void setCacheSize(int cacheSize) { mCacheSize = cacheSize; } + + inline int compactRate() const { return mCompactRate; } + inline void setCompactRate(int compactRate) { mCompactRate = compactRate; } + + inline bool enforceAccessControl() const { return mEnforceAccessControl; } + inline void setEnforceAccessControl(bool enforce) { mEnforceAccessControl = enforce; } + + inline int transactionSize() const { return mTransactionSize; } + inline void setTransactionSize(int transactionSize) { mTransactionSize = transactionSize; } + + inline bool validateSchemas() const { return mValidateSchemas; } + inline void setValidateSchemas(bool validate) { mValidateSchemas = validate; } + + inline int syncInterval() const { return mSyncInterval; } + inline void setSyncInterval(int interval) { mSyncInterval = interval; } + + inline int indexSyncInterval() const { return mIndexSyncInterval; } + inline void setIndexSyncInterval(int interval) { mIndexSyncInterval = interval; } + + inline bool debugQuery() const { return mDebugQuery; } + inline void setDebugQuery(bool debug) { mDebugQuery = debug; } + + inline QStringList configSearchPath() const { return mConfigSearchPath; } + inline void setConfigSearchPath(const QStringList &searchPath) { mConfigSearchPath = searchPath; } + + JsonDbSettings(); + +private: + void loadEnvironment(); + + bool mRejectStaleUpdates; + bool mDebug; + bool mVerbose; + bool mPerformanceLog; + int mCacheSize; + int mCompactRate; + bool mEnforceAccessControl; + int mTransactionSize; + bool mValidateSchemas; + int mSyncInterval; + int mIndexSyncInterval; + bool mDebugQuery; + QStringList mConfigSearchPath; +}; + +QT_END_NAMESPACE_JSONDB_PARTITION + +QT_END_HEADER + +#endif // JSONDB_SETTINGS_H diff --git a/src/partition/jsondbstat.h b/src/partition/jsondbstat.h new file mode 100644 index 00000000..f2d6f7ac --- /dev/null +++ b/src/partition/jsondbstat.h @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef JSONDB_STAT_H +#define JSONDB_STAT_H + +#include "jsondbpartitionglobal.h" + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE_JSONDB_PARTITION + +class Q_JSONDB_PARTITION_EXPORT JsonDbStat +{ +public: + qint32 reads; + qint32 hits; + qint32 writes; + JsonDbStat() : reads(0), hits(0), writes(0) {}; + JsonDbStat(qint32 reads, qint32 hits, qint32 writes) : reads(reads), hits(hits), writes(writes) {}; + JsonDbStat &operator += (const JsonDbStat &o) + { + reads += o.reads; + hits += o.hits; + writes += o.writes; + return *this; + }; + JsonDbStat &operator -= (const JsonDbStat &o) + { + reads = qMax((qint32)0, reads - o.reads); + hits = qMax((qint32)0, hits - o.hits); + writes = qMax((qint32)0, writes - o.writes); + return *this; + }; +}; + +QT_END_NAMESPACE_JSONDB_PARTITION + +QT_END_HEADER + +#endif // JSONDB_STAT_H diff --git a/src/partition/jsondbstrings.cpp b/src/partition/jsondbstrings.cpp new file mode 100644 index 00000000..661b941b --- /dev/null +++ b/src/partition/jsondbstrings.cpp @@ -0,0 +1,118 @@ +/**************************************************************************** +** +** 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 "jsondbstrings.h" + +QT_BEGIN_NAMESPACE_JSONDB_PARTITION + +const QString JsonDbString::kUuidStr = QString::fromLatin1("_uuid"); +const QString JsonDbString::kVersionStr = QString::fromLatin1("_version"); +const QString JsonDbString::kIdStr = QString::fromLatin1("id"); +const QString JsonDbString::kResultStr = QString::fromLatin1("result"); +const QString JsonDbString::kErrorStr = QString::fromLatin1("error"); +const QString JsonDbString::kFieldNameStr = QString::fromLatin1("fieldName"); +const QString JsonDbString::kCodeStr = QString::fromLatin1("code"); +const QString JsonDbString::kMessageStr = QString::fromLatin1("message"); +const QString JsonDbString::kNameStr = QString::fromLatin1("name"); +const QString JsonDbString::kCountStr = QString::fromLatin1("count"); +const QString JsonDbString::kCurrentStr = QString::fromLatin1("_current"); +const QString JsonDbString::kDomainStr = QString::fromLatin1("_domain"); +const QString JsonDbString::kIndexValueStr = QString::fromLatin1("_indexValue"); +const QString JsonDbString::kOwnerStr = QString::fromLatin1("_owner"); +const QString JsonDbString::kTypeStr = QString::fromLatin1("_type"); +const QString JsonDbString::kTypesStr = QString::fromLatin1("types"); +const QString JsonDbString::kParentStr = QString::fromLatin1("_parent"); +const QString JsonDbString::kSchemaTypeStr = QString::fromLatin1("_schemaType"); + +const QString JsonDbString::kActionStr = QString::fromLatin1("action"); +const QString JsonDbString::kActionsStr = QString::fromLatin1("actions"); +const QString JsonDbString::kActiveStr = QString::fromLatin1("active"); +const QString JsonDbString::kAddIndexStr = QString::fromLatin1("addIndex"); +const QString JsonDbString::kCreateStr = QString::fromLatin1("create"); +const QString JsonDbString::kDropStr = QString::fromLatin1("drop"); +const QString JsonDbString::kConflictsStr = QString::fromLatin1("conflicts"); +const QString JsonDbString::kConnectStr = QString::fromLatin1("connect"); +const QString JsonDbString::kDataStr = QString::fromLatin1("data"); +const QString JsonDbString::kDeletedStr = QString::fromLatin1("_deleted"); +const QString JsonDbString::kDisconnectStr = QString::fromLatin1("disconnect"); +const QString JsonDbString::kExplanationStr = QString::fromLatin1("explanation"); +const QString JsonDbString::kFindStr = QString::fromLatin1("find"); +const QString JsonDbString::kLengthStr = QString::fromLatin1("length"); +const QString JsonDbString::kLimitStr = QString::fromLatin1("limit"); +const QString JsonDbString::kMapTypeStr = QString::fromLatin1("Map"); +const QString JsonDbString::kMetaStr = QString::fromLatin1("_meta"); +const QString JsonDbString::kNotifyStr = QString::fromLatin1("notify"); +const QString JsonDbString::kNotificationTypeStr = QString::fromLatin1("notification"); +const QString JsonDbString::kObjectStr = QString::fromLatin1("object"); +const QString JsonDbString::kOffsetStr = QString::fromLatin1("offset"); +const QString JsonDbString::kQueryStr = QString::fromLatin1("query"); +const QString JsonDbString::kReduceTypeStr = QString::fromLatin1("Reduce"); +const QString JsonDbString::kRemoveStr = QString::fromLatin1("remove"); +const QString JsonDbString::kSchemaStr = QString::fromLatin1("schema"); +const QString JsonDbString::kUpdateStr = QString::fromLatin1("update"); +const QString JsonDbString::kTokenStr = QString::fromLatin1("token"); +const QString JsonDbString::kFlushStr = QString::fromLatin1("flush"); +const QString JsonDbString::kSettingsStr = QString::fromLatin1("settings"); +const QString JsonDbString::kViewTypeStr = QString::fromLatin1("View"); +const QString JsonDbString::kChangesSinceStr = QString::fromLatin1("changesSince"); +const QString JsonDbString::kStateNumberStr = QString::fromLatin1("stateNumber"); +const QString JsonDbString::kCollapsedStr = QString::fromLatin1("collapsed"); +const QString JsonDbString::kCurrentStateNumberStr = QString::fromLatin1("currentStateNumber"); +const QString JsonDbString::kStartingStateNumberStr = QString::fromLatin1("startingStateNumber"); +const QString JsonDbString::kTombstoneStr = QString::fromLatin1("Tombstone"); +const QString JsonDbString::kPartitionTypeStr = QString::fromLatin1("Partition"); +const QString JsonDbString::kPartitionStr = QString::fromLatin1("partition"); +const QString JsonDbString::kLogStr = QString::fromLatin1("log"); +const QString JsonDbString::kPropertyNameStr = QString::fromLatin1("propertyName"); +const QString JsonDbString::kPropertyTypeStr = QString::fromLatin1("propertyType"); +const QString JsonDbString::kPropertyFunctionStr = QString::fromLatin1("propertyFunction"); +const QString JsonDbString::kObjectTypeStr = QString::fromLatin1("objectType"); +const QString JsonDbString::kDbidTypeStr = QString::fromLatin1("DatabaseId"); +const QString JsonDbString::kIndexTypeStr = QString::fromLatin1("Index"); +const QString JsonDbString::kLocaleStr = QString::fromLatin1("locale"); +const QString JsonDbString::kCollationStr = QString::fromLatin1("collation"); +const QString JsonDbString::kCaseSensitiveStr = QString::fromLatin1("caseSensitive"); +const QString JsonDbString::kCasePreferenceStr = QString::fromLatin1("casePreference"); +const QString JsonDbString::kDatabaseSchemaVersionStr = QString::fromLatin1("databaseSchemaVersion"); +const QString JsonDbString::kPathStr = QString::fromLatin1("path"); +const QString JsonDbString::kDefaultStr = QString::fromLatin1("default"); + +QT_END_NAMESPACE_JSONDB_PARTITION diff --git a/src/partition/jsondbstrings.h b/src/partition/jsondbstrings.h new file mode 100644 index 00000000..60fb7c23 --- /dev/null +++ b/src/partition/jsondbstrings.h @@ -0,0 +1,126 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef JSONDB_STRINGS_H +#define JSONDB_STRINGS_H + +#include <QString> +#include "jsondbpartitionglobal.h" + +QT_BEGIN_NAMESPACE_JSONDB_PARTITION + +class Q_JSONDB_PARTITION_EXPORT JsonDbString { +public: + static const QString kActionStr; + static const QString kActionsStr; + static const QString kActiveStr; + static const QString kAddIndexStr; + static const QString kCodeStr; + static const QString kConflictsStr; + static const QString kConnectStr; + static const QString kCountStr; + static const QString kCreateStr; + static const QString kDropStr; + static const QString kCurrentStr; + static const QString kDataStr; + static const QString kDeletedStr; + static const QString kDisconnectStr; + static const QString kDomainStr; + static const QString kErrorStr; + static const QString kExplanationStr; + static const QString kFieldNameStr; + static const QString kFindStr; + static const QString kNameStr; + static const QString kIdStr; + static const QString kIndexValueStr; + static const QString kLengthStr; + static const QString kLimitStr; + static const QString kMapTypeStr; + static const QString kMessageStr; + static const QString kMetaStr; + static const QString kNotifyStr; + static const QString kNotificationTypeStr; + static const QString kObjectStr; + static const QString kParentStr; + static const QString kOffsetStr; + static const QString kOwnerStr; + static const QString kQueryStr; + static const QString kReduceTypeStr; + static const QString kRemoveStr; + static const QString kResultStr; + static const QString kSchemaStr; + static const QString kSchemaTypeStr; + static const QString kTypeStr; + static const QString kTypesStr; + static const QString kUpdateStr; + static const QString kUuidStr; + static const QString kVersionStr; + static const QString kViewTypeStr; + static const QString kTokenStr; + static const QString kFlushStr; + static const QString kSettingsStr; + static const QString kChangesSinceStr; + static const QString kStateNumberStr; + static const QString kCollapsedStr; + static const QString kCurrentStateNumberStr; + static const QString kStartingStateNumberStr; + static const QString kTombstoneStr; + static const QString kPartitionTypeStr; + static const QString kPartitionStr; + static const QString kLogStr; + static const QString kPropertyNameStr; + static const QString kPropertyTypeStr; + static const QString kPropertyFunctionStr; + static const QString kObjectTypeStr; + static const QString kDbidTypeStr; + static const QString kIndexTypeStr; + static const QString kLocaleStr; + static const QString kCollationStr; + static const QString kCaseSensitiveStr; + static const QString kCasePreferenceStr; + static const QString kDatabaseSchemaVersionStr; + static const QString kPathStr; + static const QString kDefaultStr; +}; + +QT_END_NAMESPACE_JSONDB_PARTITION + +#endif /* JSONDB-STRINGS_H */ diff --git a/src/partition/jsondbview.cpp b/src/partition/jsondbview.cpp new file mode 100644 index 00000000..0c862b5d --- /dev/null +++ b/src/partition/jsondbview.cpp @@ -0,0 +1,538 @@ +/**************************************************************************** +** +** 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 <QObject> +#include <QDebug> +#include <QDir> +#include <QFileInfo> +#include <QString> +#include <QElapsedTimer> + +#include "jsondbpartition.h" +#include "jsondbobject.h" +#include "jsondbview.h" +#include "jsondbmapdefinition.h" +#include "jsondbobjecttable.h" +#include "jsondbreducedefinition.h" +#include "jsondbsettings.h" +#include "jsondbscriptengine.h" + +QT_BEGIN_NAMESPACE_JSONDB_PARTITION + +JsonDbView::JsonDbView(JsonDbPartition *partition, const QString &viewType, QObject *parent) : + QObject(parent) + , mPartition(partition) + , mViewObjectTable(0) + , mMainObjectTable(mPartition->mainObjectTable()) + , mViewType(viewType) + , mUpdating(false) +{ + mViewObjectTable = new JsonDbObjectTable(mPartition); + mViewStateNumber = 0; // FIXME: should be able to read it from the object table +} + +JsonDbView::~JsonDbView() +{ + close(); + delete mViewObjectTable; +} + +void JsonDbView::open() +{ + QFileInfo fi(mPartition->filename()); + QString dirName = fi.dir().path(); + QString baseName = fi.fileName(); + baseName.replace(".db", ""); + if (!mViewObjectTable->open(QString("%1/%2-%3-View.db") + .arg(dirName) + .arg(baseName) + .arg(mViewType))) { + qCritical() << "viewDb->open" << mViewObjectTable->errorMessage(); + return; + } +} + +void JsonDbView::close() +{ + if (mViewObjectTable) + mViewObjectTable->close(); +} + +void JsonDbView::initViews(JsonDbPartition *partition) +{ + if (jsondbSettings->verbose()) + qDebug() << "Initializing views on partition" << partition->name(); + { + JsonDbObjectList mrdList = partition->getObjects(JsonDbString::kTypeStr, JsonDbString::kMapTypeStr).data; + + for (int i = 0; i < mrdList.size(); ++i) { + JsonDbObject mrd = mrdList.at(i); + JsonDbView *view = partition->addView(mrd.value("targetType").toString()); + view->createMapDefinition(mrd); + } + } + { + JsonDbObjectList mrdList = partition->getObjects(JsonDbString::kTypeStr, JsonDbString::kReduceTypeStr).data; + + for (int i = 0; i < mrdList.size(); ++i) { + JsonDbObject mrd = mrdList.at(i); + JsonDbView *view = partition->addView(mrd.value("targetType").toString()); + view->createReduceDefinition(mrd); + } + } +} + +void JsonDbView::createDefinition(JsonDbPartition *partition, QJsonObject definition) +{ + QString definitionType = definition.value(JsonDbString::kTypeStr).toString(); + QString targetType = definition.value("targetType").toString(); + JsonDbView *view = partition->findView(targetType); + if (!view) + return; + if (jsondbSettings->verbose()) + qDebug() << "createDefinition" << targetType; + if (definitionType == JsonDbString::kMapTypeStr) + view->createMapDefinition(definition); + else + view->createReduceDefinition(definition); +} +void JsonDbView::removeDefinition(JsonDbPartition *partition, QJsonObject definition) +{ + QString definitionType = definition.value(JsonDbString::kTypeStr).toString(); + QString targetType = definition.value("targetType").toString(); + JsonDbView *view = partition->findView(targetType); + if (!view) + return; + if (jsondbSettings->verbose()) + qDebug() << "removeDefinition" << targetType; + + if (definitionType == JsonDbString::kMapTypeStr) + view->removeMapDefinition(definition); + else + view->removeReduceDefinition(definition); +} + + +void JsonDbView::createMapDefinition(QJsonObject mapDefinition) +{ + QString targetType = mapDefinition.value("targetType").toString(); + QString uuid = mapDefinition.value(JsonDbString::kUuidStr).toString(); + if (jsondbSettings->verbose()) + qDebug() << "createMapDefinition" << uuid << targetType << "{"; + + JsonDbOwner *owner = new JsonDbOwner(this); + 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]; + mMapDefinitionsBySource.insert(sourceType, def); + } + mMapDefinitions.insert(def->uuid(), def); + updateSourceTypesList(); + if (jsondbSettings->verbose()) + qDebug() << "createMapDefinition" << uuid << targetType << "}"; +} + +void JsonDbView::removeMapDefinition(QJsonObject mapDefinition) +{ + QString targetType = mapDefinition.value("targetType").toString(); + QString uuid = mapDefinition.value(JsonDbString::kUuidStr).toString(); + if (jsondbSettings->verbose()) + qDebug() << "removeMapDefinition" << uuid << targetType << "{"; + foreach (JsonDbMapDefinition *d, mMapDefinitions) { + if (d->uuid() == uuid) { + JsonDbMapDefinition *def = d; + mMapDefinitions.remove(def->uuid()); + const QStringList &sourceTypes = def->sourceTypes(); + for (int i = 0; i < sourceTypes.size(); i++) + mMapDefinitionsBySource.remove(sourceTypes[i]); + break; + } + } + + updateSourceTypesList(); + if (jsondbSettings->verbose()) + qDebug() << "removeMapDefinition" << uuid << targetType << "}"; +} + +void JsonDbView::createReduceDefinition(QJsonObject reduceDefinition) +{ + QString targetType = reduceDefinition.value("targetType").toString(); + QString sourceType = reduceDefinition.value("sourceType").toString(); + if (jsondbSettings->debug()) + qDebug() << "createReduceDefinition" << sourceType << targetType << sourceType << "{"; + + JsonDbOwner *owner = new JsonDbOwner(this); + owner->setAllowAll(true); + JsonDbReduceDefinition *def = new JsonDbReduceDefinition(owner, mPartition, reduceDefinition, this); + def->initIndexes(); + mReduceDefinitionsBySource.insert(sourceType, def); + mReduceDefinitions.insert(def->uuid(), def); + + updateSourceTypesList(); + if (jsondbSettings->verbose()) + qDebug() << "createReduceDefinition" << sourceType << targetType << "}"; +} + +void JsonDbView::removeReduceDefinition(QJsonObject reduceDefinition) +{ + QString targetType = reduceDefinition.value("targetType").toString(); + QString sourceType = reduceDefinition.value("sourceType").toString(); + QString uuid = reduceDefinition.value(JsonDbString::kUuidStr).toString(); + + if (jsondbSettings->verbose()) + qDebug() << "removeReduceDefinition" << sourceType << targetType << "{"; + + foreach (JsonDbReduceDefinition *d, mReduceDefinitionsBySource.values(sourceType)) { + if (d->uuid() == uuid) { + JsonDbReduceDefinition *def = d; + mReduceDefinitionsBySource.remove(def->sourceType()); + mReduceDefinitions.remove(def->uuid()); + break; + } + } + + updateSourceTypesList(); + //TODO: actually remove the table + if (jsondbSettings->verbose()) + qDebug() << "removeReduceDefinition" << sourceType << targetType << "}"; +} + +void JsonDbView::updateSourceTypesList() +{ + QSet<QString> sourceTypes; + foreach (const JsonDbMapDefinition *d, mMapDefinitions) { + foreach (const QString sourceType, d->sourceTypes()) + sourceTypes.insert(sourceType); + } + foreach (const JsonDbReduceDefinition *d, mReduceDefinitions) + sourceTypes.insert(d->sourceType()); + + ObjectTableSourceTypeMap objectTableSourceTypeMap; + for (QSet<QString>::const_iterator it = sourceTypes.begin(); + it != sourceTypes.end(); + ++it) { + QString sourceType = *it; + JsonDbObjectTable *objectTable = mPartition->findObjectTable(sourceType); + objectTableSourceTypeMap[objectTable].insert(sourceType); + } + mSourceTypes = sourceTypes.toList(); + mObjectTableSourceTypeMap = objectTableSourceTypeMap; +} + +void JsonDbView::updateView(quint32 desiredStateNumber, JsonDbUpdateList *resultingChanges) +{ + QElapsedTimer timer; + if (jsondbSettings->performanceLog()) + timer.start(); + if (jsondbSettings->verbose()) + qDebug() << "updateView" << mViewType << "{"; + // current state of the main object table of the partition + quint32 partitionStateNumber = mMainObjectTable->stateNumber(); + quint32 viewStateNumber = mViewStateNumber; + + // if the view is up to date, then return + if ((desiredStateNumber && viewStateNumber >= desiredStateNumber) + || viewStateNumber == partitionStateNumber) { + if (jsondbSettings->verbose()) + qDebug() << "updateView" << mViewType << "}"; + return; + } + if (mUpdating) { + if (jsondbSettings->verbose()) + qDebug() << "Update already in progess" + << "updateView" << mViewType << "}"; + return; + } + mUpdating = true; + + // update the source types in case they are views + for (JsonDbView::ObjectTableSourceTypeMap::const_iterator it = mObjectTableSourceTypeMap.begin(); + it != mObjectTableSourceTypeMap.end(); + ++it) { + const QSet<QString> &sourceTypes = it.value(); + QString sourceType = *sourceTypes.begin(); + // update the source tables (as indicated by one of the source types) + mPartition->updateView(sourceType); + } + + // The Uuids of the definitions processed for the first time by processUpdatedDefinitions. + QSet<QString> processedDefinitionUuids; + + // find any Map or Reduce definitions that have been updated from + // states viewStateNumber to partitionStateNumber and process them. + bool inTransaction = + processUpdatedDefinitions(mViewType, viewStateNumber, processedDefinitionUuids); + // Do not process them again during this update of the view. + + if (jsondbSettings->verbose()) + qDebug() << "processedDefinitionUuids" << processedDefinitionUuids; + // if there were any updated definitions, then it would have + // already begun the transaction on the view's mObjectTable. + if (!inTransaction) + mViewObjectTable->begin(); + inTransaction = true; + + // now process the changes on each of the source tables + for (ObjectTableSourceTypeMap::const_iterator it = mObjectTableSourceTypeMap.begin(); + it != mObjectTableSourceTypeMap.end(); + ++it) { + JsonDbObjectTable *sourceTable = it.key(); + const QSet<QString> &sourceTypes = it.value(); + if (sourceTable->stateNumber() == viewStateNumber) + // up-to-date with respect this source table + continue; + + QList<JsonDbUpdate> changeList; + sourceTable->changesSince(viewStateNumber, sourceTypes, &changeList, JsonDbObjectTable::SplitTypeChanges); + updateViewOnChanges(changeList, processedDefinitionUuids, resultingChanges); + } + mViewStateNumber = partitionStateNumber; + mUpdating = false; + JsonDbScriptEngine::scriptEngine()->collectGarbage(); + if (inTransaction) + mViewObjectTable->commit(partitionStateNumber); + if (jsondbSettings->verbose()) + qDebug() << "}" << "updateView" << mViewType << partitionStateNumber; + if (jsondbSettings->performanceLog()) + qDebug() << "updateView" << "stateNumber" << mViewStateNumber << mViewType << timer.elapsed() << "ms"; + + emit updated(mViewType); +} + +void JsonDbView::updateEagerView(const JsonDbUpdateList &objectsUpdated, JsonDbUpdateList *resultingChanges) +{ + quint32 partitionStateNumber = mMainObjectTable->stateNumber(); + quint32 viewStateNumber = mViewStateNumber; + + // make sure we can run this set of updates + if (mViewStateNumber != (partitionStateNumber - 1) + || viewDefinitionUpdated(objectsUpdated)) { + // otherwise do a full update + if (jsondbSettings->verbose()) + qDebug() << "updateEagerView" << mViewType << "full update" + << "viewStateNumber" << mViewStateNumber << "partitionStateNumber" << partitionStateNumber + << (viewDefinitionUpdated(objectsUpdated) ? "definition updated" : ""); + updateView(partitionStateNumber, resultingChanges); + return; + } + + QElapsedTimer timer; + if (jsondbSettings->performanceLog()) + timer.start(); + if (jsondbSettings->verbose()) + qDebug() << "updateEagerView" << mViewType << "{"; + + // begin transaction + mViewObjectTable->begin(); + + // then do the update + QSet<QString> processedDefinitionUuids; + updateViewOnChanges(objectsUpdated, processedDefinitionUuids, resultingChanges); + + // end transaction + JsonDbScriptEngine::scriptEngine()->collectGarbage(); + mViewObjectTable->commit(partitionStateNumber); + mViewStateNumber = partitionStateNumber; + + if (jsondbSettings->verbose()) + qDebug() << "updateEagerView" << mViewType << viewStateNumber << "}"; + if (jsondbSettings->performanceLog()) + qDebug() << "updateEagerView" << "stateNumber" << mViewStateNumber << mViewType << timer.elapsed() << "ms"; +} + +// Updates the in-memory state numbers on the view so that we know it +// has seen all relevant updates from this transaction +void JsonDbView::updateViewStateNumber(quint32 partitionStateNumber) +{ + // If the change is not zero or one it's an error + if (jsondbSettings->verbose() || (mViewStateNumber != (partitionStateNumber - 1) && mViewStateNumber != partitionStateNumber)) + qCritical() << "updateViewStateNumber" << mViewType << "viewStateNumber" << mViewStateNumber << "partitionStateNumber" << partitionStateNumber; + mViewStateNumber = 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 false; +} + +void JsonDbView::updateViewOnChanges(const JsonDbUpdateList &objectsUpdated, + QSet<QString> &processedDefinitionUuids, + JsonDbUpdateList *changeList) +{ + 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, changeList); + } else if (mMapDefinitionsBySource.contains(afterType)) { + JsonDbMapDefinition *def = mMapDefinitionsBySource.value(afterType); + if (processedDefinitionUuids.contains(def->uuid())) + continue; + def->updateObject(beforeObject, afterObject, changeList); + } + + if (mReduceDefinitionsBySource.contains(beforeType)) { + JsonDbReduceDefinition *def = mReduceDefinitionsBySource.value(beforeType); + if (processedDefinitionUuids.contains(def->uuid())) + continue; + def->updateObject(beforeObject, afterObject, changeList); + } else if (mReduceDefinitionsBySource.contains(afterType)) { + JsonDbReduceDefinition *def = mReduceDefinitionsBySource.value(afterType); + if (processedDefinitionUuids.contains(def->uuid())) + continue; + def->updateObject(beforeObject, afterObject, changeList); + } + } +} + +bool JsonDbView::processUpdatedDefinitions(const QString &viewType, quint32 targetStateNumber, + QSet<QString> &processedDefinitionUuids) +{ + bool inTransaction = false; + quint32 stateNumber = mMainObjectTable->stateNumber(); + if (stateNumber == targetStateNumber) + return inTransaction; + QSet<QString> limitTypes; + limitTypes << JsonDbString::kMapTypeStr << JsonDbString::kReduceTypeStr; + QList<JsonDbUpdate> changeList; + mMainObjectTable->changesSince(targetStateNumber, limitTypes, &changeList, JsonDbObjectTable::SplitTypeChanges); + foreach (const JsonDbUpdate &change, changeList) { + QString definitionUuid; + JsonDbNotification::Action action = change.action; + JsonDbObject before = change.oldObject; + JsonDbObject after = change.newObject; + QString beforeType = before.value(JsonDbString::kTypeStr).toString(); + QString afterType = after.value(JsonDbString::kTypeStr).toString(); + if (jsondbSettings->verbose()) + qDebug() << "definition change" << change; + if (action != JsonDbNotification::Create) { + if (limitTypes.contains(beforeType) + && (before.value("targetType").toString() == viewType)) { + if (!inTransaction) { + mViewObjectTable->begin(); + inTransaction = true; + } + definitionUuid = before.value(JsonDbString::kUuidStr).toString(); + QString definitionType = before.value(JsonDbString::kTypeStr).toString(); + QString targetType = before.value("targetType").toString(); + if (definitionType == JsonDbString::kMapTypeStr) + JsonDbMapDefinition::definitionRemoved(mPartition, mViewObjectTable, targetType, definitionUuid); + else + JsonDbReduceDefinition::definitionRemoved(mPartition, mViewObjectTable, targetType, definitionUuid); + } + } + if (action != JsonDbNotification::Delete) { + if ((limitTypes.contains(afterType)) + && (after.value("targetType").toString() == viewType)) { + if (!inTransaction) { + mViewObjectTable->begin(); + inTransaction = true; + } + definitionUuid = after.value(JsonDbString::kUuidStr).toString(); + QString definitionType = after.value(JsonDbString::kTypeStr).toString(); + 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()) + processedDefinitionUuids.insert(definitionUuid); + } + return inTransaction; +} + +void JsonDbView::reduceMemoryUsage() +{ + mViewObjectTable->flushCaches(); + + for (QMap<QString,JsonDbMapDefinition*>::iterator it = mMapDefinitions.begin(); + it != mMapDefinitions.end(); + ++it) + it.value()->releaseScriptEngine(); + for (QMap<QString,JsonDbReduceDefinition*>::iterator it = mReduceDefinitions.begin(); + it != mReduceDefinitions.end(); + ++it) + it.value()->releaseScriptEngine(); +} + +bool JsonDbView::isActive() const +{ + foreach (JsonDbMapDefinition *mapDef, mMapDefinitions) { + if (mapDef->isActive()) + return true; + } + + foreach (JsonDbReduceDefinition *reduceDef, mReduceDefinitions) { + if (reduceDef->isActive()) + return true; + } + + return false; +} + +#include "moc_jsondbview.cpp" + +QT_END_NAMESPACE_JSONDB_PARTITION diff --git a/src/partition/jsondbview.h b/src/partition/jsondbview.h new file mode 100644 index 00000000..70a1bda4 --- /dev/null +++ b/src/partition/jsondbview.h @@ -0,0 +1,123 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef JSONDB_VIEW_H +#define JSONDB_VIEW_H + +#include <QObject> +#include <QString> + +#include "jsondbpartitionglobal.h" +#include "jsondbmapdefinition.h" +#include "jsondbreducedefinition.h" + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE_JSONDB_PARTITION + +class JsonDbOwner; +class JsonDbPartition; +class JsonDbObjectTable; + +class Q_JSONDB_PARTITION_EXPORT JsonDbView : public QObject +{ + Q_OBJECT +public: + JsonDbView(JsonDbPartition *partition, const QString &viewType, + QObject *parent = 0); + ~JsonDbView(); + 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(); + + static void initViews(JsonDbPartition *partition); + static void createDefinition(JsonDbPartition *partition, QJsonObject definition); + static void removeDefinition(JsonDbPartition *partition, QJsonObject definition); + + void updateView(quint32 stateNumber=0, JsonDbUpdateList *resultingChanges=0); + void updateEagerView(const JsonDbUpdateList &objectsUpdated, JsonDbUpdateList *resultingChanges); + void updateViewStateNumber(quint32 partitionStateNumber); + void reduceMemoryUsage(); + + bool isActive() const; + +Q_SIGNALS: + void updated(const QString &type); + +private: + void createMapDefinition(QJsonObject mapDefinition); + void removeMapDefinition(QJsonObject mapDefinition); + void createReduceDefinition(QJsonObject reduceDefinition); + void removeReduceDefinition(QJsonObject reduceDefinition); + 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, JsonDbUpdateList *changeList); + +private: + JsonDbPartition *mPartition; + JsonDbObjectTable *mViewObjectTable; // view object table + JsonDbObjectTable *mMainObjectTable; // partition's main object table + quint32 mViewStateNumber; + QString mViewType; + QStringList mSourceTypes; + typedef QMap<JsonDbObjectTable*,QSet<QString> > ObjectTableSourceTypeMap; + ObjectTableSourceTypeMap mObjectTableSourceTypeMap; + QMap<QString,JsonDbMapDefinition*> mMapDefinitions; // maps uuid to view definition + QMap<QString,JsonDbMapDefinition*> mMapDefinitionsBySource; // maps map source type to view definition + QMap<QString,JsonDbReduceDefinition*> mReduceDefinitions; // maps uuid to view definition + QMap<QString,JsonDbReduceDefinition*> mReduceDefinitionsBySource; // maps reduce source type to view definition + bool mUpdating; + + friend class JsonDbMapDefinition; + friend class JsonDbReduceDefinition; +}; + +QT_END_NAMESPACE_JSONDB_PARTITION + +QT_END_HEADER + +#endif // JSONDB_VIEW_H diff --git a/src/partition/partition.pro b/src/partition/partition.pro new file mode 100644 index 00000000..063e5d48 --- /dev/null +++ b/src/partition/partition.pro @@ -0,0 +1,78 @@ +TEMPLATE = lib +TARGET = $$QT.jsondbpartition.name +MODULE = jsondbpartition + +load(qt_module) +load(qt_module_config) + +DESTDIR = $$QT.jsondbpartition.libs +VERSION = $$QT.jsondbpartition.VERSION +DEFINES += QT_JSONDB_PARTITION_LIB + +QT = core network qml + +CONFIG += module create_prl +MODULE_PRI = ../../modules/qt_jsondbpartition.pri + +include(../3rdparty/btree/btree.pri) +include(../hbtree/hbtree.pri) + +RESOURCES = jsondb.qrc + +HEADERS += \ + jsondbowner.h \ + jsondbproxy.h \ + jsondbindex.h \ + jsondbobject.h \ + jsondbpartition.h \ + jsondbquery.h \ + jsondbstat.h \ + jsondbview.h \ + jsondbmapdefinition.h \ + jsondbnotification.h \ + jsondbobjectkey.h \ + jsondbobjecttable.h \ + jsondbbtree.h \ + jsondbobjecttypes_impl_p.h \ + jsondbobjecttypes_p.h \ + jsondbreducedefinition.h \ + schema-validation/checkpoints.h \ + schema-validation/object.h \ + jsondbschemamanager_impl_p.h \ + jsondbschemamanager_p.h \ + jsondbscriptengine.h \ + jsondbsettings.h \ + jsondbindexquery.h \ + jsondberrors.h \ + jsondbstrings.h \ + jsondbpartitionglobal.h \ + jsondbcollator.h \ + jsondbcollator_p.h + +SOURCES += \ + jsondbowner.cpp \ + jsondbproxy.cpp \ + jsondbindex.cpp \ + jsondbobject.cpp \ + jsondbpartition.cpp \ + jsondbquery.cpp \ + jsondbview.cpp \ + jsondbmapdefinition.cpp \ + jsondbnotification.cpp \ + jsondbobjecttable.cpp \ + jsondbbtree.cpp \ + jsondbreducedefinition.cpp \ + jsondbscriptengine.cpp \ + jsondbsettings.cpp \ + jsondbindexquery.cpp \ + jsondberrors.cpp \ + jsondbstrings.cpp \ + jsondbcollator.cpp + +mac:QMAKE_FRAMEWORK_BUNDLE_NAME = $$QT.jsondbpartition.name + +contains(config_test_icu, yes) { + LIBS += -licuuc -licui18n +} else { + DEFINES += NO_COLLATION_SUPPORT +} diff --git a/src/partition/schema-validation/checkpoints.h b/src/partition/schema-validation/checkpoints.h new file mode 100644 index 00000000..34e2e164 --- /dev/null +++ b/src/partition/schema-validation/checkpoints.h @@ -0,0 +1,837 @@ +/**************************************************************************** +** +** 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 <QtCore/qhash.h> +#include <QtCore/qlist.h> +#include <QtCore/qregexp.h> +#include "object.h" + +#ifndef CHECKPOINTS_H +#define CHECKPOINTS_H + +QT_BEGIN_HEADER + +namespace SchemaValidation { + +/** + \internal + This template is used for hash computation for static latin1 strings. + */ +template<ushort C1 = 0, ushort C2 = 0, ushort C3 = 0, ushort C4 = 0, ushort C5 = 0, + ushort C6 = 0, ushort C7 = 0, ushort C8 = 0, ushort C9 = 0, ushort C10 = 0, + ushort C11 = 0, ushort C12 = 0, ushort C13 = 0, ushort C14 = 0, ushort C15 = 0, + ushort C16 = 0, ushort C17 = 0, ushort C18 = 0> +struct QStaticStringHash +{ + typedef QStaticStringHash<C2, C3, C4, C5, C6, C7, C8, C9, C10, C11, C12, C13, C14, C15, C16, C17, C18> Suffix; + + const static int Hash = (C1 ^ Suffix::Hash) + 5; + //(C1 ^ ( (C2 ^ (...)) +5 )) +5 +}; + +template<> +struct QStaticStringHash<> +{ + typedef QStaticStringHash<> Suffix; + const static int Hash = 0; + + /** + \internal + This function has to be align with qStringHash::Hash value + */ + inline static int hash(const QString &string) + { + const ushort *str = reinterpret_cast<const ushort*>(string.constData()); + return hash(str, 0, string.length()); + } +private: + inline static int hash(const ushort *str, const int index, const int length) + { + return index != length ? (str[index] ^ hash(str, index + 1, length)) + 5 + : 0; + } +}; + +template<class T> +class SchemaPrivate<T>::NullCheck : public Check { +public: + NullCheck(SchemaPrivate *schema) + : Check(schema, "") // TODO + {} +protected: + virtual bool doCheck(const Value&) { return true; } +}; + +// 5.1 +template<class T> +class SchemaPrivate<T>::CheckType : public Check { + enum Type {StringType = 0x0001, + NumberType = 0x0002, + IntegerType = 0x0004, + BooleanType = 0x0008, + ObjectType = 0x0010, + ArrayType = 0x0020, + NullType = 0x0040, + AnyType = 0x0080, + UnknownType = 0}; +public: + CheckType(SchemaPrivate *schema, const Value& type) + : Check(schema, "Type check failed for %1") + , m_type(UnknownType) + { + bool ok; + QStringList typesName; + + QString typeName = type.toString(&ok); + if (!ok) { + ValueList typesList = type.toList(&ok); + Q_ASSERT_X(ok, Q_FUNC_INFO, "Type is neither a string nor an array"); + typename ValueList::const_iterator i; + for (i = typesList.constBegin(); i != typesList.constEnd(); ++i) { + typeName = (*i).toString(&ok); + if (ok) { + typesName << typeName; + } + } + } else { + typesName << typeName; + } + foreach (const QString &name, typesName) { + const int hash = QStaticStringHash<>::hash(name.toLower()); + + // FIXME there are chances of conflicts, do we care? What is chance for new types in JSON? + // FIXME we need to check only 2 chars. That would be faster. + // FIXME probably we want to support schemas here too. + switch (hash) { + case QStaticStringHash<'s','t','r','i','n','g'>::Hash: + m_type |= StringType; + break; + case QStaticStringHash<'n','u','m','b','e','r'>::Hash: + m_type |= NumberType; + m_type |= IntegerType; // an integer is also a number + break; + case QStaticStringHash<'i','n','t','e','g','e','r'>::Hash: + m_type |= IntegerType; + break; + case QStaticStringHash<'b','o','o','l','e','a','n'>::Hash: + m_type |= BooleanType; + break; + case QStaticStringHash<'o','b','j','e','c','t'>::Hash: + m_type |= ObjectType; + break; + case QStaticStringHash<'a','r','r','a','y'>::Hash: + m_type |= ArrayType; + break; + case QStaticStringHash<'a','n','y'>::Hash: + m_type |= AnyType; + break; + case QStaticStringHash<'n','u','l','l'>::Hash: + m_type |= NullType; + break; + default: + m_type |= UnknownType; + } + } + +// qDebug() << Q_FUNC_INFO << m_type << type.toString(&ok); + } + + virtual bool doCheck(const Value &value) + { + if (m_type == UnknownType) + return true; + + bool result = findType(value) & m_type; +// bool ok; +// qDebug() << Q_FUNC_INFO << findType(value) << m_type << value.toString(&ok) << result; + return result; + } + +private: + inline Type findType(const Value &value) const + { + bool ok; + // Lets assume that the value is valid. + switch (m_type) { + case StringType: + value.toString(&ok); + if (ok) + return StringType; + break; + case NumberType: + value.toDouble(&ok); + if (ok) + return NumberType; + break; + case IntegerType: + value.toInt(&ok); + if (ok) + return IntegerType; + break; + case BooleanType: + value.toBool(&ok); + if (ok) + return BooleanType; + break; + case ObjectType: + value.toObject(&ok); + if (ok) + return ObjectType; + break; + case NullType: + value.toNull(&ok); + if (ok) + return NullType; + break; + case AnyType: + return AnyType; + case UnknownType: + break; + default: + break; + }; + + //TODO FIXME it can be better + value.toInt(&ok); + if (ok) + return IntegerType; + value.toDouble(&ok); + if (ok) + return NumberType; + value.toObject(&ok); + if (ok) + return ObjectType; + value.toString(&ok); + if (ok) + return StringType; + value.toBool(&ok); + if (ok) + return BooleanType; + value.toList(&ok); + if (ok) + return ArrayType; + return AnyType; + } + + uint m_type; +}; + +// 5.2 +template<class T> +class SchemaPrivate<T>::CheckProperties : public Check { +public: + CheckProperties(SchemaPrivate *schema, const Value &properties) + : Check(schema, "Properties check failed for %1") + { + bool ok; + const Object obj = properties.toObject(&ok); + Q_ASSERT(ok); + + QList<Key> propertyNames = obj.propertyNames(); +// qDebug() << " propertyNames: " << propertyNames <<this; + + m_checks.reserve(propertyNames.count()); + foreach (const Key &propertyName, propertyNames) { + QVarLengthArray<Check *, 4> checks; + const Object propertyChecks = obj.property(propertyName).toObject(&ok); +// qDebug() << " propertyChecks:" << propertyChecks; + + Q_ASSERT(ok); + foreach (const Key &key, propertyChecks.propertyNames()) { +// bool ok; +// qDebug() << " key:" << key << this << propertyChecks.property(key).toString(&ok)<< propertyChecks.property(key).toInt(&ok); + checks.append(schema->createCheckPoint(key, propertyChecks.property(key))); + } + m_checks.insert(propertyName, checks); + } + } + + ~CheckProperties() + { + typename QHash<const Key, QVarLengthArray<Check *, 4> >::const_iterator i; + for (i = m_checks.constBegin(); i != m_checks.constEnd(); ++i) { + typename QVarLengthArray<Check *, 4>::const_iterator j; + for (j = i.value().constBegin(); j != i.value().constEnd(); ++j){ + delete *j; + } + } + } + + virtual bool doCheck(const Value &value) + { + bool ok; + Object object = value.toObject(&ok); + if (!ok) + return false; + + //qDebug() << Q_FUNC_INFO; + foreach (const Key &key, object.propertyNames()) { + QVarLengthArray<Check *, 4> empty; + QVarLengthArray<Check *, 4> checks = m_checks.value(key, empty); + Value property = object.property(key); + foreach (Check *check, checks) { + //qDebug() <<"CHECKING:" << check; + if (!check->check(property)) { + return false; + } + } + } + return true; + } +private: + QHash<const Key, QVarLengthArray<Check *, 4> > m_checks; +}; + +// 5.5 +template<class T> +class SchemaPrivate<T>::CheckItems : public Check { +public: + CheckItems(SchemaPrivate *schemap, const Value &schema) + : Check(schemap, "Items check failed for %1") + { + // qDebug() << Q_FUNC_INFO << this; + bool ok; + Object obj = schema.toObject(&ok); + Q_ASSERT(ok); + m_schema = Schema<T>(obj, schemap->m_callbacks); + } + + virtual bool doCheck(const Value& value) + { + //qDebug() << Q_FUNC_INFO << this; + bool ok; + ValueList array = value.toList(&ok); + if (!ok) + return false; + + typename ValueList::const_iterator i; + for (i = array.constBegin(); i != array.constEnd(); ++i) { + if (!m_schema.check(*i, Check::m_schema->m_callbacks)) { + return false; + } + } + return true; + } +private: + Schema<T> m_schema; +}; + +// 5.7 +template<class T> +class SchemaPrivate<T>::CheckRequired : public Check { +public: + CheckRequired(SchemaPrivate *schema, const Value &required) + : Check(schema, "Check required field") // TODO what to do about Required ? + { + bool ok; + m_req = required.toBool(&ok); + if (!ok) { + // maybe someone used string instead of bool + QString value = required.toString(&ok).toLower(); + if (value == QString::fromLatin1("false")) + m_req = false; + else if (value == QString::fromLatin1("true")) + m_req = true; + else + Q_ASSERT(false); + + qWarning() << QString::fromLatin1("Wrong 'required' syntax found, instead of boolean type a string was used"); + } + Q_ASSERT(ok); + if (m_req) + Check::m_schema->m_maxRequired++; + } + + virtual bool doCheck(const Value&) + { + //qDebug() << Q_FUNC_INFO << m_schema << this; + if (m_req) + Check::m_schema->m_requiredCount++; + return true; + } +private: + bool m_req; +}; + +// 5.9 +template<class T> +class SchemaPrivate<T>::CheckMinimum : public Check { +public: + CheckMinimum(SchemaPrivate *schema, const Value &minimum) + : Check(schema, "Minimum check failed for %1") + { + bool ok; + m_min = minimum.toDouble(&ok); + Q_ASSERT(ok); + } + + virtual bool doCheck(const Value &value) + { + bool ok; + return value.toDouble(&ok) >= m_min && ok; + } +private: + double m_min; +}; + +// 5.10 +template<class T> +class SchemaPrivate<T>::CheckMaximum : public Check { +public: + CheckMaximum(SchemaPrivate *schema, const Value &maximum) + : Check(schema, "Maximum check failed for %1") + { + // qDebug() << Q_FUNC_INFO << this; + bool ok; + m_max = maximum.toDouble(&ok); + Q_ASSERT(ok); + } + + virtual bool doCheck(const Value &value) + { + //qDebug() << Q_FUNC_INFO << value << m_max << this; + bool ok; + return value.toDouble(&ok) <= m_max && ok; + } +private: + double m_max; +}; + + +// 5.11 +template<class T> +class SchemaPrivate<T>::CheckExclusiveMinimum : public Check { +public: + CheckExclusiveMinimum(SchemaPrivate *schema, const Value &minimum) + : Check(schema, "Exclusive minimum check failed for %1") + { + bool ok; + m_min = minimum.toDouble(&ok); + Q_ASSERT(ok); + } + + virtual bool doCheck(const Value &value) + { + bool ok; + return value.toDouble(&ok) > m_min && ok; + } +private: + double m_min; +}; + +// 5.12 +template<class T> +class SchemaPrivate<T>::CheckExclusiveMaximum : public Check { +public: + CheckExclusiveMaximum(SchemaPrivate *schema, const Value &maximum) + : Check(schema, "Exclusive minimum check failed for %1") + { + bool ok; + m_max = maximum.toDouble(&ok); + Q_ASSERT(ok); + } + + virtual bool doCheck(const Value &value) + { + bool ok; + return value.toDouble(&ok) < m_max && ok; + } +private: + double m_max; +}; + +// 5.13 +template<class T> +class SchemaPrivate<T>::CheckMinItems : public Check { +public: + CheckMinItems(SchemaPrivate *schema, const Value& minimum) + : Check(schema, "Minimum item count check failed for %1") + { + bool ok; + m_min = minimum.toInt(&ok); + Q_ASSERT(ok); + } + + virtual bool doCheck(const Value &value) + { + bool ok; + int count = value.toList(&ok).size(); + return count >= m_min && ok; + } +private: + int m_min; +}; + +// 5.14 +template<class T> +class SchemaPrivate<T>::CheckMaxItems : public Check { +public: + CheckMaxItems(SchemaPrivate *schema, const Value& maximum) + : Check(schema, "Maximum item count check failed for %1") + { + bool ok; + m_max = maximum.toInt(&ok); + Q_ASSERT(ok); + } + + virtual bool doCheck(const Value &value) + { + bool ok; + int count = value.toList(&ok).size(); + return count <= m_max && ok; + } + +private: + int m_max; +}; + +// 5.16 +template<class T> +class SchemaPrivate<T>::CheckPattern : public Check { +public: + CheckPattern(SchemaPrivate *schema, const Value& patternValue) + : Check(schema, "Pattern check failed for %1") + { + bool ok; + QString patternString = patternValue.toString(&ok); + m_regexp.setPattern(patternString); + Q_ASSERT(ok && m_regexp.isValid()); + } + + virtual bool doCheck(const Value &value) + { + bool ok; + QString str = value.toString(&ok); + if (!ok) { + // According to spec (5.15) we should check the value only when it exist and it is a string. + // It is a bit strange, but I think we have to return true here. + return true; + } + return m_regexp.exactMatch(str); + } +private: + QRegExp m_regexp; +}; + +// 5.17 +template<class T> +class SchemaPrivate<T>::CheckMinLength : public Check { +public: + CheckMinLength(SchemaPrivate *schema, const Value& min) + : Check(schema, "Minimal string length check failed for %1") + { + bool ok; + m_min = min.toInt(&ok); + Q_ASSERT(ok); + } + + virtual bool doCheck(const Value &value) + { + bool ok; + QString str = value.toString(&ok); +// qDebug() << Q_FUNC_INFO << str << ok; + if (!ok) { + // According to spec (5.16) we should check the value only when it exist and it is a string. + // It is a bit strange, but I think we have to return true here. + return true; + } + return str.length() >= m_min; + } +private: + int m_min; +}; + +// 5.18 +template<class T> +class SchemaPrivate<T>::CheckMaxLength : public Check { +public: + CheckMaxLength(SchemaPrivate *schema, const Value& max) + : Check(schema, "Maximal string length check failed for %1") + { + bool ok; + m_max = max.toInt(&ok); + Q_ASSERT(ok); + } + + virtual bool doCheck(const Value &value) + { + bool ok; + QString str = value.toString(&ok); +// qDebug() << Q_FUNC_INFO << str << ok; + if (!ok) { + // According to spec (5.16) we should check the value only when it exist and it is a string. + // It is a bit strange, but I think we have to return true here. + return true; + } + return str.length() <= m_max; + } +private: + int m_max; +}; + +// 5.26 +template<class T> +class SchemaPrivate<T>::CheckExtends : public Check { +public: + CheckExtends(SchemaPrivate *schema, const Value &value) + : Check(schema, "Extends check failed for %1") + { + // FIXME + // Keep in mind that there is a bug in spec. (internet draft 3). + // We should search for a schema not for a string here. + // Tests are using "string" syntax, so we need to support it for a while + bool ok; + Object obj = value.toObject(&ok); + if (!ok) { + QString schemaName = value.toString(&ok); + if (!ok) { + ValueList array = value.toList(&ok); + Q_ASSERT(ok); + typename ValueList::const_iterator i; + for (i = array.constBegin(); i != array.constEnd(); ++i) { + Object obj = (*i).toObject(&ok); + Q_ASSERT(ok); + m_extendedSchema.append(Schema<T>(obj, schema->m_callbacks)); + } + } else { + qWarning() << QString::fromLatin1("Wrong 'extends' syntax found, instead of \"%1\" should be \"%2\"") + .arg(schemaName, QString::fromLatin1("{\"$ref\":\"%1\"}").arg(schemaName)); + m_extendedSchema.append(schema->m_callbacks->loadSchema(schemaName)); + } + } else { + m_extendedSchema.append(Schema<T>(obj, schema->m_callbacks)); + } + } + + virtual bool doCheck(const Value &value) + { + for (int i = 0; i < m_extendedSchema.count(); ++i) { + if (!m_extendedSchema[i].check(value, Check::m_schema->m_callbacks)) + return false; + } + return true; + } +private: + QVarLengthArray<Schema<T>, 4> m_extendedSchema; +}; + +// 5.28 +template<class T> +class SchemaPrivate<T>::CheckRef : public Check { +public: + CheckRef(SchemaPrivate *schema, const Value &value) + : Check(schema, "$Ref check failed for %1") + { + // TODO according to spec we should replace existing check by this one + // I'm not sure what does it mean. Should we remove other checks? + // What if we have two $ref? Can it happen? For now, lets use magic of + // undefined bahaviour (without crashing of course). + bool ok; + QString schemaName = value.toString(&ok); + Q_ASSERT(ok); + + m_newSchema = schema->m_callbacks->loadSchema(schemaName); + if (!m_newSchema.isValid()) { + // FIXME should we have current schema name? + const QString msg = QString::fromLatin1("Schema extends %1 but it is unknown.") + .arg(schemaName); + qWarning() << msg; + schema->m_callbacks->setError(msg); + } + } + virtual bool doCheck(const Value &value) + { + bool result = m_newSchema.check(value, Check::m_schema->m_callbacks); +// qDebug() << Q_FUNC_INFO << result; + return result; + } +private: + Schema<T> m_newSchema; +}; + +template<class T> +class SchemaPrivate<T>::CheckDescription : public NullCheck { +public: + CheckDescription(SchemaPrivate *schema) + : NullCheck(schema) + {} +}; + +template<class T> +class SchemaPrivate<T>::CheckTitle : public NullCheck { +public: + CheckTitle(SchemaPrivate *schema) + : NullCheck(schema) + {} +}; + +template<class T> +typename SchemaPrivate<T>::Check *SchemaPrivate<T>::createCheckPoint(const Key &key, const Value &value) +{ + QString keyName = key; + keyName = keyName.toLower(); + + // This is a perfect hash. BUT spec, in future, can be enriched by new values, that we should just ignore. + // As we do not know about them we can't be sure that our perfect hash will be still perfect, therefore + // we have to do additional string comparison that confirm result hash function result. + int hash = QStaticStringHash<>::hash(keyName); + switch (hash) { + case QStaticStringHash<'r','e','q','u','i','r','e','d'>::Hash: + if (QString::fromLatin1("required") == keyName) + return new CheckRequired(this, value); + break; + case QStaticStringHash<'m','a','x','i','m','u','m'>::Hash: + if (QString::fromLatin1("maximum") == keyName) + return new CheckMaximum(this, value); + break; + case QStaticStringHash<'e','x','c','l','u','s','i','v','e','m','a','x','i','m','u','m'>::Hash: + if (QString::fromLatin1("exclusivemaximum") == keyName) + return new CheckExclusiveMaximum(this, value); + break; + case QStaticStringHash<'m','i','n','i','m','u','m'>::Hash: + if (QString::fromLatin1("minimum") == keyName) + return new CheckMinimum(this, value); + break; + case QStaticStringHash<'e','x','c','l','u','s','i','v','e','m','i','n','i','m','u','m'>::Hash: + if (QString::fromLatin1("exclusiveminimum") == keyName) + return new CheckExclusiveMinimum(this, value); + break; + case QStaticStringHash<'p','r','o','p','e','r','t','i','e','s'>::Hash: + if (QString::fromLatin1("properties") == keyName) + return new CheckProperties(this, value); + break; + case QStaticStringHash<'d','e','s','c','r','i','p','t','i','o','n'>::Hash: + if (QString::fromLatin1("description") == keyName) + return new CheckDescription(this); + break; + case QStaticStringHash<'t','i','t','l','e'>::Hash: + if (QString::fromLatin1("title") == keyName) + return new CheckTitle(this); + break; + case QStaticStringHash<'m','a','x','i','t','e','m','s'>::Hash: + if (QString::fromLatin1("maxitems") == keyName) + return new CheckMaxItems(this,value); + break; + case QStaticStringHash<'m','i','n','i','t','e','m','s'>::Hash: + if (QString::fromLatin1("minitems") == keyName) + return new CheckMinItems(this,value); + break; + case QStaticStringHash<'i','t','e','m','s'>::Hash: + if (QString::fromLatin1("items") == keyName) + return new CheckItems(this,value); + break; + case QStaticStringHash<'e','x','t','e','n','d','s'>::Hash: + if (QString::fromLatin1("extends") == keyName) + return new CheckExtends(this,value); + break; + case QStaticStringHash<'p','a','t','t','e','r','n'>::Hash: + if (QString::fromLatin1("pattern") == keyName) + return new CheckPattern(this, value); + break; + case QStaticStringHash<'m','i','n','l','e','n','g','t','h'>::Hash: + if (QString::fromLatin1("minlength") == keyName) + return new CheckMinLength(this, value); + break; + case QStaticStringHash<'m','a','x','l','e','n','g','t','h'>::Hash: + if (QString::fromLatin1("maxlength") == keyName) + return new CheckMaxLength(this, value); + break; + case QStaticStringHash<'$','r','e','f'>::Hash: + if (QString::fromLatin1("$ref") == keyName) + return new CheckRef(this, value); + break; + case QStaticStringHash<'t','y','p','e'>::Hash: + if (QString::fromLatin1("type") == keyName) + return new CheckType(this, value); + break; + default: +// qDebug() << "NOT FOUND" << keyName; + return new NullCheck(this); + } + +// qDebug() << "FALLBACK" << keyName; +// bool ok; +// qCritical() << keyName << value.toString(&ok); + return new NullCheck(this); +} + +template<class T> +bool Schema<T>::check(const Value &value, Service *callbackToUseForCheck) const +{ + return d_ptr->check(value, callbackToUseForCheck); +} + +template<class T> +bool SchemaPrivate<T>::check(const Value &value, Service *callbackToUseForCheck) const +{ + //qDebug() << Q_FUNC_INFO << m_checks.count() << this; + Q_ASSERT(callbackToUseForCheck); + Q_ASSERT(!m_callbacks); + + m_callbacks = callbackToUseForCheck; + bool result = check(value); + m_callbacks = 0; + return result; +} + +template<class T> +bool SchemaPrivate<T>::check(const Value &value) const +{ + Q_ASSERT(m_callbacks); + + m_requiredCount = 0; + foreach (Check *check, m_checks) { + if (!check->check(value)) { + return false; + } + } + if (m_requiredCount != m_maxRequired) { + m_callbacks->setError(QString::fromLatin1("Schema validation error: Required field is missing")); + return false; + } + return true; +} + +} // namespace SchemaValidation + +QT_END_HEADER + +#endif // CHECKPOINTS_H diff --git a/src/partition/schema-validation/object.h b/src/partition/schema-validation/object.h new file mode 100644 index 00000000..2725647e --- /dev/null +++ b/src/partition/schema-validation/object.h @@ -0,0 +1,256 @@ +/**************************************************************************** +** +** 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 <QtCore/qlist.h> +#include <QtCore/qvarlengtharray.h> +#include <QtCore/qstring.h> +#include <QtCore/qhash.h> +#include <QtCore/qshareddata.h> + +#ifndef OBJECT_H +#define OBJECT_H + +QT_BEGIN_HEADER + +namespace SchemaValidation { + +///** +// Interface for object like classes. Instances of specialization for this class would be +// used as input data for schema validation +// \internal +//*/ +//template<class T> +//class Object +//{ +//public: +// /** +// Should return value of a property with the given \a name +// */ +// Value<T> property(const Key<T>& name) const; + +// /** +// Should return list of all properties name +// \todo replace to iterator syntax +// */ +// QList<Key<T> > propertyNames() const; + +//}; + +///** +// Interface for value like classes. A value can be one of types defined types in JSON +// \internal +//*/ +//template<class T> +//class Value { +//public: +// int toInt(bool *ok) const; +// double toDouble(bool *ok) const; +// ValueList toList(bool *ok) const; +// QString toString(bool *ok) const; +// bool toBool(bool *ok) const; +// void toNull(bool *ok) const; +// Object<T> toObject(bool *ok) const; +//}; +// ValueList::count() +// ValueList::constBegin() +// ValueList::constEnd() +// ValueList::const_iterator() +// +// void Service::setError(const QString &message) +// Schema<T> Service::loadSchema(const QString &name) + +template<class T> +class SchemaPrivate; + +template<class T> +class Schema { + typedef typename T::Value Value; + typedef typename T::Key Key; + typedef typename T::Object Object; + typedef typename T::ValueList ValueList; + typedef typename T::Service Service; + +public: + inline bool check(const Value &value, Service *callbackToUseForCheck) const; + + Schema() + : d_ptr(new SchemaPrivate<T>()) + {} + + Schema(const Object& schema, Service *callbacksToUseForCompilation) + : d_ptr(new SchemaPrivate<T>()) + { + d_ptr->compile(schema, callbacksToUseForCompilation); + } + + bool isValid() const + { + return d_ptr->isValid(); + } +private: + + friend class SchemaPrivate<T>; + QExplicitlySharedDataPointer<SchemaPrivate<T> > d_ptr; +}; + +template<class T> +class SchemaPrivate : public QSharedData +{ + typedef typename Schema<T>::Value Value; + typedef typename Schema<T>::Key Key; + typedef typename Schema<T>::Object Object; + typedef typename Schema<T>::ValueList ValueList; + typedef typename Schema<T>::Service Service; + + class Check { + public: + Check(SchemaPrivate *schema, const char* errorMessage) + : m_schema(schema) + , m_errorMessage(errorMessage) + { + Q_ASSERT(schema); + Q_ASSERT(errorMessage); + } + virtual ~Check() {} + bool check(const Value& value) + { + bool result = doCheck(value); + if (!result) { + bool ok; + // TODO it is tricky, as we do not have access to source code of this schema. + // maybe we can set "additional, hidden " source property in each schema property, or some nice hash? + m_schema->m_callbacks->setError("Schema validation error: " + QString::fromLatin1(m_errorMessage).arg(value.toString(&ok))); + } + return result; + } + + protected: + SchemaPrivate *m_schema; + // return true if it is ok + virtual bool doCheck(const Value&) = 0; + private: + const char *m_errorMessage; + }; + + // empty check + class NullCheck; + // 5.1 + class CheckType; + // 5.2 + class CheckProperties; + // 5.5 + class CheckItems; + // 5.7 + class CheckRequired; + // 5.9 + class CheckMinimum; + // 5.10 + class CheckMaximum; + // 5.11 + class CheckExclusiveMinimum; + // 5.12 + class CheckExclusiveMaximum; + // 5.13 + class CheckMinItems; + // 5.14 + class CheckMaxItems; + // 5.16 + class CheckPattern; + // 5.17 + class CheckMinLength; + // 5.18 + class CheckMaxLength; + // 5.21 + class CheckTitle; + // 5.26 + class CheckExtends; + // 5.28 + class CheckRef; + class CheckDescription; + + inline Check *createCheckPoint(const Key &key, const Value &value); + inline bool check(const Value &value) const; + +public: + SchemaPrivate() + : m_maxRequired(0) + , m_requiredCount(0) + , m_callbacks(0) + {} + ~SchemaPrivate() + { + for (int i = 0; i < m_checks.count(); ++i) + delete m_checks.at(i); + } + + inline bool check(const Value &value, Service *callbackToUseForCheck) const; + inline void compile(const Object &schema, Service *callbackToUseForCompile) + { + Q_ASSERT(callbackToUseForCompile); + m_callbacks = callbackToUseForCompile; + const QList<Key> checksKeys = schema.propertyNames(); + m_checks.reserve(checksKeys.count()); + foreach (const Key &key, checksKeys) { + m_checks.append(createCheckPoint(key, schema.property(key))); + } + m_callbacks = 0; + } + + bool isValid() const + { + // we have some checks so it means that something got compiled. + return m_checks.size(); + } + +private: + QVarLengthArray<Check *, 4> m_checks; + qint32 m_maxRequired; + mutable qint32 m_requiredCount; + mutable Service *m_callbacks; +}; + +} + +#include "checkpoints.h" + +QT_END_HEADER + +#endif // OBJECT_H diff --git a/src/partition/schema/Capability.json b/src/partition/schema/Capability.json new file mode 100644 index 00000000..27f49859 --- /dev/null +++ b/src/partition/schema/Capability.json @@ -0,0 +1,33 @@ +{ + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Name of the capability" + }, + "partition": { + "type":"string", + "description": "partition where this capability should be used" + }, + + "accessRules": { + "type": "object", + "patternProperties": { + ".*": { + "type": "array", + "items": { + "type": "string", + "description": "A JsonDb query string. Objects matching this query will be allowed for this access type." + } + } + } + }, + "quotas": { + "type": "object", + "storage": { + "type": "number", + "description": "Current only one in quotas." + } + } + } +} diff --git a/src/partition/schema/Index.json b/src/partition/schema/Index.json new file mode 100644 index 00000000..7c60d153 --- /dev/null +++ b/src/partition/schema/Index.json @@ -0,0 +1,24 @@ +{ + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Name of the index. Default to the value of propertyName, if non-empty. Required if propertyFunction is specified." + }, + "propertyName": { + "type": "string", + "description": "Property to index." + }, + "propertyFunction": { + "type": "string", + "description": "String that evaluates to a function that emits the index values. Mutually exclusive with propertyName." + }, + "propertyType": { + "type": "string", + "description": "Type of values stored in that property." + }, + "objectType": { + "description": "Object type to index. Optional." + } + } +} diff --git a/src/partition/schema/RootCapability.json b/src/partition/schema/RootCapability.json new file mode 100644 index 00000000..9d4869fa --- /dev/null +++ b/src/partition/schema/RootCapability.json @@ -0,0 +1,17 @@ +{ + "_type": "Capability", + "name": "root", + "partition": "all", + "accessRules": { + "rw": { + "read": [".*"], + "write": [".*"] + }, + "setOwner": { + "setOwner": [".*"] + } + }, + "quotas": { + "storage": -1 + } +} diff --git a/src/partition/schema/View.json b/src/partition/schema/View.json new file mode 100644 index 00000000..6130fa33 --- /dev/null +++ b/src/partition/schema/View.json @@ -0,0 +1,10 @@ +{ + "type": "object", + "properties": { + "sourceUuids": { + "type": "array" + }, + "key": { + } + } +} diff --git a/src/partition/schema/notification.json b/src/partition/schema/notification.json new file mode 100644 index 00000000..360be3b2 --- /dev/null +++ b/src/partition/schema/notification.json @@ -0,0 +1,12 @@ +{ + "type": "object", + "ephemeral": "true", + "properties": { + "actions": { + "type": "array" + }, + "query": { + "type": "string" + } + } +} |