File indexing completed on 2024-05-12 05:26:05

0001 /*
0002  * Copyright (C) 2014 Aaron Seigo <aseigo@kde.org>
0003  * Copyright (C) 2014 Christian Mollekopf <mollekopf@kolabsys.com>
0004  *
0005  * This library is free software; you can redistribute it and/or
0006  * modify it under the terms of the GNU Lesser General Public
0007  * License as published by the Free Software Foundation; either
0008  * version 2.1 of the License, or (at your option) version 3, or any
0009  * later version accepted by the membership of KDE e.V. (or its
0010  * successor approved by the membership of KDE e.V.), which shall
0011  * act as a proxy defined in Section 6 of version 3 of the license.
0012  *
0013  * This library is distributed in the hope that it will be useful,
0014  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0015  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0016  * Lesser General Public License for more details.
0017  *
0018  * You should have received a copy of the GNU Lesser General Public
0019  * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
0020  */
0021 
0022 #include "storage.h"
0023 
0024 #include "log.h"
0025 #include "utils.h"
0026 
0027 QDebug& operator<<(QDebug &dbg, const Sink::Storage::DataStore::Error &error)
0028 {
0029     dbg << error.message << "Code: " << error.code << "Db: " << error.store;
0030     return dbg;
0031 }
0032 
0033 namespace Sink {
0034 namespace Storage {
0035 
0036 QMap<QByteArray, int> DataStore::baseDbs()
0037 {
0038     return {{"revisionType", Storage::IntegerKeys},
0039             {"revisions", Storage::IntegerKeys},
0040             {"uidsToRevisions", Storage::AllowDuplicates | Storage::IntegerValues},
0041             {"default", 0},
0042             {"__metadata", 0},
0043             {"__flagtable", 0}};
0044 }
0045 
0046 DbLayout::DbLayout()
0047 {
0048 
0049 }
0050 
0051 DbLayout::DbLayout(const QByteArray &n, const Databases &t)
0052     : name(n),
0053     tables(t)
0054 {
0055 
0056 }
0057 
0058 void errorHandler(const DataStore::Error &error)
0059 {
0060     if (error.code == DataStore::TransactionError) {
0061         SinkError() << "Transaction error:" << error;
0062     } else {
0063         SinkWarning() << "Database error:" << error;
0064     }
0065 }
0066 
0067 std::function<void(const DataStore::Error &error)> DataStore::basicErrorHandler()
0068 {
0069     return errorHandler;
0070 }
0071 
0072 void DataStore::setDefaultErrorHandler(const std::function<void(const DataStore::Error &error)> &errorHandler)
0073 {
0074     mErrorHandler = errorHandler;
0075 }
0076 
0077 std::function<void(const DataStore::Error &error)> DataStore::defaultErrorHandler() const
0078 {
0079     if (mErrorHandler) {
0080         return mErrorHandler;
0081     }
0082     return basicErrorHandler();
0083 }
0084 
0085 void DataStore::setMaxRevision(DataStore::Transaction &transaction, qint64 revision)
0086 {
0087     transaction.openDatabase("__metadata").write("maxRevision", QByteArray::number(revision));
0088 }
0089 
0090 qint64 DataStore::maxRevision(const DataStore::Transaction &transaction)
0091 {
0092     qint64 r = 0;
0093     transaction.openDatabase("__metadata").scan("maxRevision",
0094         [&](const QByteArray &, const QByteArray &revision) -> bool {
0095             r = revision.toLongLong();
0096             return false;
0097         },
0098         [](const Error &error) {
0099             if (error.code != DataStore::NotFound) {
0100                 SinkWarning() << "Couldn't find the maximum revision: " << error;
0101             }
0102         });
0103     return r;
0104 }
0105 
0106 void DataStore::setCleanedUpRevision(DataStore::Transaction &transaction, qint64 revision)
0107 {
0108     transaction.openDatabase("__metadata").write("cleanedUpRevision", QByteArray::number(revision));
0109 }
0110 
0111 qint64 DataStore::cleanedUpRevision(const DataStore::Transaction &transaction)
0112 {
0113     qint64 r = 0;
0114     transaction.openDatabase("__metadata").scan("cleanedUpRevision",
0115         [&](const QByteArray &, const QByteArray &revision) -> bool {
0116             r = revision.toLongLong();
0117             return false;
0118         },
0119         [](const Error &error) {
0120             if (error.code != DataStore::NotFound) {
0121                 SinkWarning() << "Couldn't find the cleanedUpRevision: " << error;
0122             }
0123         });
0124     return r;
0125 }
0126 
0127 Identifier DataStore::getUidFromRevision(const DataStore::Transaction &transaction, size_t revision)
0128 {
0129     QByteArray uid;
0130     transaction
0131         .openDatabase("revisions", /* errorHandler = */ {}, IntegerKeys)
0132         .scan(revision,
0133             [&](const size_t, const QByteArray &value) -> bool {
0134                 uid = QByteArray{ value.constData(), value.size() };
0135                 return false;
0136             },
0137             [revision, &transaction](const Error &error) {
0138                 //This can fail if we attempt to replay from a removed revision that has been cleaned up already.
0139                 SinkError() << "Couldn't find uid for revision: " << revision << error.message;
0140                 SinkTrace() << "Cleaned up revision: " << cleanedUpRevision(transaction);
0141             });
0142     Q_ASSERT(!uid.isEmpty());
0143     return Identifier::fromInternalByteArray(uid);
0144 }
0145 
0146 size_t DataStore::getLatestRevisionFromUid(DataStore::Transaction &t, const Identifier &uid)
0147 {
0148     size_t revision = 0;
0149     t.openDatabase("uidsToRevisions", {}, AllowDuplicates | IntegerValues)
0150         .findLast(uid.toInternalByteArray(), [&revision](const QByteArray &key, const QByteArray &value) {
0151             revision = byteArrayToSizeT(value);
0152         },
0153         [](const Error &error) {
0154             //This is expected if we attempt to lookup an id that doesn't exist.
0155             if (error.code != DataStore::NotFound) {
0156                 SinkWarning() << "Error during getLatestRevisionFromUid: " << error;
0157             }
0158         });
0159 
0160     return revision;
0161 }
0162 
0163 QList<size_t> DataStore::getRevisionsUntilFromUid(DataStore::Transaction &t, const Identifier &uid, size_t lastRevision)
0164 {
0165     QList<size_t> queriedRevisions;
0166     t.openDatabase("uidsToRevisions", {}, AllowDuplicates | IntegerValues)
0167         .scan(uid.toInternalByteArray(), [&queriedRevisions, lastRevision](const QByteArray &, const QByteArray &value) {
0168             const size_t currentRevision = byteArrayToSizeT(value);
0169             if (currentRevision < lastRevision) {
0170                 queriedRevisions << currentRevision;
0171                 return true;
0172             }
0173 
0174             return false;
0175         });
0176 
0177     return queriedRevisions;
0178 }
0179 
0180 QList<size_t> DataStore::getRevisionsFromUid(DataStore::Transaction &t, const Identifier &uid)
0181 {
0182     return getRevisionsUntilFromUid(t, uid, SIZE_MAX);
0183 }
0184 
0185 QByteArray DataStore::getTypeFromRevision(const DataStore::Transaction &transaction, size_t revision)
0186 {
0187     QByteArray type;
0188     transaction.openDatabase("revisionType", /* errorHandler = */ {}, IntegerKeys)
0189         .scan(revision,
0190             [&](const size_t, const QByteArray &value) -> bool {
0191                 type = QByteArray{value.constData(), value.size()};
0192                 return false;
0193             },
0194             [revision](const Error &error) { SinkWarning() << "Couldn't find type for revision " << revision; });
0195     Q_ASSERT(!type.isEmpty());
0196     return type;
0197 }
0198 
0199 void DataStore::recordRevision(DataStore::Transaction &transaction, size_t revision,
0200     const Identifier &uid, const QByteArray &type)
0201 {
0202     const auto uidBa = uid.toInternalByteArray();
0203     transaction
0204         .openDatabase("revisions", /* errorHandler = */ {}, IntegerKeys)
0205         .write(revision, uidBa);
0206     transaction.openDatabase("uidsToRevisions", /* errorHandler = */ {}, AllowDuplicates | IntegerValues)
0207         .write(uidBa, sizeTToByteArray(revision));
0208     transaction
0209         .openDatabase("revisionType", /* errorHandler = */ {}, IntegerKeys)
0210         .write(revision, type);
0211 }
0212 
0213 void DataStore::removeRevision(DataStore::Transaction &transaction, size_t revision)
0214 {
0215     const auto uid = getUidFromRevision(transaction, revision);
0216 
0217     transaction
0218         .openDatabase("revisions", /* errorHandler = */ {}, IntegerKeys)
0219         .remove(revision);
0220     transaction.openDatabase("uidsToRevisions", /* errorHandler = */ {}, AllowDuplicates | IntegerValues)
0221         .remove(uid.toInternalByteArray(), sizeTToByteArray(revision));
0222     transaction
0223         .openDatabase("revisionType", /* errorHandler = */ {}, IntegerKeys)
0224         .remove(revision);
0225 }
0226 
0227 void DataStore::recordUid(DataStore::Transaction &transaction, const Identifier &uid, const QByteArray &type)
0228 {
0229     transaction.openDatabase(type + "uids", {}, IntegerKeys).write(uid.toInternalByteArray(), "");
0230 }
0231 
0232 void DataStore::removeUid(DataStore::Transaction &transaction, const Identifier &uid, const QByteArray &type)
0233 {
0234     transaction.openDatabase(type + "uids", {}, IntegerKeys).remove(uid.toInternalByteArray());
0235 }
0236 
0237 void DataStore::getUids(const QByteArray &type, const Transaction &transaction, const std::function<void(const Identifier &uid)> &callback)
0238 {
0239     transaction.openDatabase(type + "uids", {}, IntegerKeys).scan("", [&] (const QByteArray &key, const QByteArray &) {
0240         callback(Identifier::fromInternalByteArray(key));
0241         return true;
0242     });
0243 }
0244 
0245 bool DataStore::hasUid(const QByteArray &type, const Transaction &transaction, const Identifier &uid)
0246 {
0247     bool hasTheUid = false;
0248     transaction.openDatabase(type + "uids", {}, IntegerKeys).scan(uid.toInternalByteArray(), [&](const QByteArray &key, const QByteArray &) {
0249         Q_ASSERT(uid.toInternalByteArray() == key);
0250         hasTheUid = true;
0251         return false;
0252     });
0253 
0254     return hasTheUid;
0255 }
0256 
0257 QByteArray DataStore::generateUid()
0258 {
0259     return createUuid();
0260 }
0261 
0262 DataStore::NamedDatabase DataStore::mainDatabase(const DataStore::Transaction &t, const QByteArray &type)
0263 {
0264     if (type.isEmpty()) {
0265         SinkError() << "Tried to open main database for empty type.";
0266         Q_ASSERT(false);
0267         return {};
0268     }
0269     return t.openDatabase(type + ".main", /* errorHandler= */ {}, IntegerKeys);
0270 }
0271 
0272 bool DataStore::NamedDatabase::contains(const QByteArray &uid)
0273 {
0274     bool found = false;
0275     scan(uid,
0276         [&found](const QByteArray &, const QByteArray &) -> bool {
0277             found = true;
0278             return false;
0279         },
0280         [](const DataStore::Error &error) {}, true);
0281     return found;
0282 }
0283 
0284 void DataStore::setDatabaseVersion(DataStore::Transaction &transaction, qint64 revision)
0285 {
0286     transaction.openDatabase("__metadata").write("databaseVersion", QByteArray::number(revision));
0287 }
0288 
0289 qint64 DataStore::databaseVersion(const DataStore::Transaction &transaction)
0290 {
0291     qint64 r = 0;
0292     transaction.openDatabase("__metadata").scan("databaseVersion",
0293         [&](const QByteArray &, const QByteArray &revision) -> bool {
0294             r = revision.toLongLong();
0295             return false;
0296         },
0297         [](const Error &error) {
0298             if (error.code != DataStore::NotFound) {
0299                 SinkWarning() << "Couldn't find the database version: " << error;
0300             }
0301         });
0302     return r;
0303 }
0304 
0305 
0306 }
0307 } // namespace Sink