File indexing completed on 2024-04-14 03:49:37

0001 /*
0002     This file is part of the KDE Baloo project.
0003     SPDX-FileCopyrightText: 2015 Vishesh Handa <vhanda@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.1-or-later
0006 */
0007 
0008 #include "writetransaction.h"
0009 #include "dbstate.h"
0010 #include "database.h"
0011 #include "idutils.h"
0012 
0013 #include <QTest>
0014 #include <QTemporaryDir>
0015 
0016 using namespace Baloo;
0017 
0018 class WriteTransactionTest : public QObject
0019 {
0020     Q_OBJECT
0021 private Q_SLOTS:
0022     void init() {
0023         dir = std::make_unique<QTemporaryDir>();
0024         db = std::make_unique<Database>(dir->path());
0025         db->open(Database::CreateDatabase);
0026         m_dirId = filePathToId(QFile::encodeName(dir->path()));
0027         QVERIFY(m_dirId);
0028     }
0029 
0030     void cleanup() {
0031         db.reset();
0032         dir.reset();
0033     }
0034 
0035     void testAddDocument();
0036     void testAddDocumentTwoDocuments();
0037     void testAddAndRemoveOneDocument();
0038     void testAddAndReplaceOneDocument();
0039     void testIdempotentDocumentChange();
0040 
0041     void testRemoveRecursively();
0042     void testDocumentId();
0043     void testTermPositions();
0044     void testTransactionReset();
0045 private:
0046     std::unique_ptr<QTemporaryDir> dir;
0047     std::unique_ptr<Database> db;
0048     quint64 m_dirId = 0;
0049 };
0050 
0051 void WriteTransactionTest::testAddDocument()
0052 {
0053     Transaction tr(db.get(), Transaction::ReadWrite);
0054 
0055     const QString filePath(dir->path() + QStringLiteral("/file"));
0056     QByteArray url = QFile::encodeName(filePath);
0057     quint64 id = 99;
0058 
0059     QCOMPARE(tr.hasDocument(id), false);
0060 
0061     Document doc;
0062     doc.setId(id);
0063     doc.setParentId(m_dirId);
0064     doc.setUrl(url);
0065     doc.addTerm("a");
0066     doc.addTerm("ab");
0067     doc.addTerm("abc");
0068     doc.addTerm("power");
0069     doc.addFileNameTerm("link");
0070     doc.addXattrTerm("system");
0071     doc.setMTime(1);
0072     doc.setCTime(2);
0073 
0074     tr.addDocument(doc);
0075     tr.commit();
0076 
0077     Transaction tr2(db.get(), Transaction::ReadOnly);
0078 
0079     DBState state;
0080     state.postingDb = {{"a", {id}}, {"ab", {id}}, {"abc", {id}}, {"power", {id}}, {"system", {id}}, {"link", {id}}};
0081     state.positionDb = {};
0082     state.docTermsDb = {{id, {"a", "ab", "abc", "power"} }};
0083     state.docFileNameTermsDb = {{id, {"link"} }};
0084     state.docXAttrTermsDb = {{id, {"system"} }};
0085     state.docTimeDb = {{id, DocumentTimeDB::TimeInfo(1, 2)}};
0086     state.mtimeDb = {{1, id}};
0087 
0088     DBState actualState = DBState::fromTransaction(&tr2);
0089     QCOMPARE(actualState, state);
0090 }
0091 
0092 static Document createDocument(const QString& filePath, quint32 mtime, quint32 ctime, const QVector<QByteArray>& terms,
0093                                const QVector<QByteArray>& fileNameTerms, const QVector<QByteArray>& xattrTerms, quint64 parentId)
0094 {
0095     Document doc;
0096 
0097     // Generate a unique file id. A monotonic counter is sufficient here.
0098     static quint64 fileid = parentId;
0099     fileid++;
0100 
0101     const QByteArray url = QFile::encodeName(filePath);
0102     doc.setId(fileid);
0103     doc.setParentId(parentId);
0104     doc.setUrl(url);
0105 
0106     for (const QByteArray& term: terms) {
0107         doc.addTerm(term);
0108     }
0109     for (const QByteArray& term: fileNameTerms) {
0110         doc.addFileNameTerm(term);
0111     }
0112     for (const QByteArray& term: xattrTerms) {
0113         doc.addXattrTerm(term);
0114     }
0115     doc.setMTime(mtime);
0116     doc.setCTime(ctime);
0117 
0118     return doc;
0119 }
0120 
0121 void WriteTransactionTest::testAddDocumentTwoDocuments()
0122 {
0123     const QString url1(dir->path() + QStringLiteral("/file1"));
0124     const QString url2(dir->path() + QStringLiteral("/file2"));
0125 
0126     Document doc1 = createDocument(url1, 5, 1, {"a", "abc", "dab"}, {"file1"}, {}, m_dirId);
0127     Document doc2 = createDocument(url2, 6, 2, {"a", "abcd", "dab"}, {"file2"}, {}, m_dirId);
0128 
0129     {
0130         Transaction tr(db.get(), Transaction::ReadWrite);
0131         tr.addDocument(doc1);
0132         tr.addDocument(doc2);
0133         tr.commit();
0134     }
0135 
0136     Transaction tr(db.get(), Transaction::ReadOnly);
0137 
0138     quint64 id1 = doc1.id();
0139     quint64 id2 = doc2.id();
0140 
0141     DBState state;
0142     state.postingDb = {{"a", {id1, id2}}, {"abc", {id1}}, {"abcd", {id2}}, {"dab", {id1, id2}}, {"file1", {id1}}, {"file2", {id2}}};
0143     state.positionDb = {};
0144     state.docTermsDb = {{id1, {"a", "abc", "dab"}}, {id2, {"a", "abcd", "dab"}}};
0145     state.docFileNameTermsDb = {{id1, {"file1"}}, {id2, {"file2"}}};
0146     state.docXAttrTermsDb = {};
0147     state.docTimeDb = {{id1, DocumentTimeDB::TimeInfo(5, 1)}, {id2, DocumentTimeDB::TimeInfo(6, 2)}};
0148     state.mtimeDb = {{5, id1}, {6, id2}};
0149 
0150     DBState actualState = DBState::fromTransaction(&tr);
0151     QVERIFY(DBState::debugCompare(actualState, state));
0152 }
0153 
0154 void WriteTransactionTest::testAddAndRemoveOneDocument()
0155 {
0156     const QString url1(dir->path() + QStringLiteral("/file1"));
0157 
0158     Document doc1 = createDocument(url1, 5, 1, {"a", "abc", "dab"}, {"file1"}, {}, m_dirId);
0159 
0160     {
0161         Transaction tr(db.get(), Transaction::ReadWrite);
0162         tr.addDocument(doc1);
0163         tr.commit();
0164     }
0165     {
0166         Transaction tr(db.get(), Transaction::ReadWrite);
0167         tr.removeDocument(doc1.id());
0168         tr.commit();
0169     }
0170 
0171     Transaction tr(db.get(), Transaction::ReadOnly);
0172     DBState actualState = DBState::fromTransaction(&tr);
0173     QVERIFY(DBState::debugCompare(actualState, DBState()));
0174 }
0175 
0176 void WriteTransactionTest::testAddAndReplaceOneDocument()
0177 {
0178     const QString url1(dir->path() + QStringLiteral("/file1"));
0179 
0180     Document doc1 = createDocument(url1, 5, 1, {"a", "abc", "dab"}, {"file1"}, {}, m_dirId);
0181     Document doc2 = createDocument(url1, 6, 2, {"a", "abc", "xxx"}, {"file1", "yyy"}, {}, m_dirId);
0182     quint64 id = doc1.id();
0183     doc2.setId(id);
0184 
0185     {
0186         Transaction tr(db.get(), Transaction::ReadWrite);
0187         tr.addDocument(doc1);
0188         tr.commit();
0189     }
0190 
0191     DBState state;
0192     state.postingDb = {{"a", {id}}, {"abc", {id}}, {"dab", {id}}, {"file1", {id}} };
0193     state.positionDb = {};
0194     state.docTermsDb = {{id, {"a", "abc", "dab"} }};
0195     state.docFileNameTermsDb = {{id, {"file1"} }};
0196     state.docXAttrTermsDb = {};
0197     state.docTimeDb = {{id, DocumentTimeDB::TimeInfo(5, 1)}};
0198     state.mtimeDb = {{5, id}};
0199 
0200     {
0201         Transaction tr(db.get(), Transaction::ReadOnly);
0202         DBState actualState = DBState::fromTransaction(&tr);
0203         QVERIFY(DBState::debugCompare(actualState, state));
0204     }
0205 
0206     {
0207         Transaction tr(db.get(), Transaction::ReadWrite);
0208         tr.replaceDocument(doc2, DocumentOperation::Everything);
0209         tr.commit();
0210     }
0211 
0212     state.postingDb = {{"a", {id}}, {"abc", {id}}, {"xxx", {id}}, {"file1", {id}}, {"yyy", {id}} };
0213     state.docTermsDb = {{id, {"a", "abc", "xxx"} }};
0214     state.docFileNameTermsDb = {{id, {"file1", "yyy"} }};
0215     state.docTimeDb = {{id, DocumentTimeDB::TimeInfo(6, 2)}};
0216     state.mtimeDb = {{6, id}};
0217 
0218     Transaction tr(db.get(), Transaction::ReadOnly);
0219     DBState actualState = DBState::fromTransaction(&tr);
0220     QVERIFY(DBState::debugCompare(actualState, state));
0221 }
0222 
0223 void WriteTransactionTest::testRemoveRecursively()
0224 {
0225     const QString path = dir->path();
0226     const QString url1(path + QStringLiteral("/file1"));
0227     const QString dirPath(path + QStringLiteral("/dir"));
0228     const QString url2(dirPath + QStringLiteral("/file1"));
0229 
0230     Document doc1 = createDocument(url1, 5, 1, {"a", "abc", "dab"}, {"file1"}, {}, m_dirId);
0231     Document doc2 = createDocument(dirPath, 5, 1, {"a"}, {"dir"}, {}, m_dirId);
0232     Document doc3 = createDocument(url2, 5, 1, {"a", "abc", "dab"}, {"file1"}, {}, doc2.id());
0233 
0234     {
0235         Transaction tr(db.get(), Transaction::ReadWrite);
0236         tr.addDocument(doc1);
0237         tr.addDocument(doc2);
0238         tr.addDocument(doc3);
0239         tr.commit();
0240     }
0241     {
0242         Transaction tr(db.get(), Transaction::ReadWrite);
0243         tr.removeRecursively(m_dirId);
0244         tr.commit();
0245     }
0246 
0247     Transaction tr(db.get(), Transaction::ReadOnly);
0248     DBState actualState = DBState::fromTransaction(&tr);
0249     QVERIFY(DBState::debugCompare(actualState, DBState()));
0250 }
0251 
0252 void WriteTransactionTest::testDocumentId()
0253 {
0254     const QString url1(dir->path() + QStringLiteral("/file1"));
0255 
0256     Document doc1 = createDocument(url1, 5, 1, {"a", "abc", "dab"}, {"file1"}, {}, m_dirId);
0257 
0258     {
0259         Transaction tr(db.get(), Transaction::ReadWrite);
0260         tr.addDocument(doc1);
0261         tr.commit();
0262     }
0263 
0264     Transaction tr(db.get(), Transaction::ReadOnly);
0265     QCOMPARE(tr.documentId(doc1.url()), doc1.id());
0266 }
0267 
0268 void WriteTransactionTest::testTermPositions()
0269 {
0270     const QString url1(dir->path() + QStringLiteral("/file1"));
0271     const QString url2(dir->path() + QStringLiteral("/file2"));
0272     const QString url3(dir->path() + QStringLiteral("/file3"));
0273 
0274     Document doc1 = createDocument(url1, 5, 1, {"a", "abc", "dab"}, {"file1"}, {}, m_dirId);
0275     quint64 id1 = doc1.id();
0276     Document doc2 = createDocument(url2, 5, 2, {"a", "abcd"}, {"file2"}, {}, m_dirId);
0277     quint64 id2 = doc2.id();
0278     Document doc3 = createDocument(url3, 6, 3, {"dab"}, {"file3"}, {}, m_dirId);
0279     quint64 id3 = doc3.id();
0280 
0281     {
0282         Transaction tr(db.get(), Transaction::ReadWrite);
0283         tr.addDocument(doc1);
0284         tr.addDocument(doc2);
0285         tr.addDocument(doc3);
0286         tr.commit();
0287     }
0288 
0289     DBState state;
0290     state.postingDb = {{"a", {id1, id2}}, {"abc", {id1}}, {"abcd", {id2}}, {"dab", {id1, id3}}, {"file1", {id1}}, {"file2", {id2}}, {"file3", {id3}} };
0291     state.positionDb = {};
0292     state.docTermsDb = {{id1, {"a", "abc", "dab"}}, {id2, {"a", "abcd"}}, {id3, {"dab"}} };
0293     state.docFileNameTermsDb = {{id1, {"file1"}}, {id2, {"file2"}}, {id3, {"file3"}} };
0294     state.docXAttrTermsDb = {};
0295     state.docTimeDb = {{id1, DocumentTimeDB::TimeInfo(5, 1)}, {id2, DocumentTimeDB::TimeInfo(5, 2)}, {id3, DocumentTimeDB::TimeInfo(6, 3)} };
0296     state.mtimeDb = {{5, id1}, {5, id2}, {6, id3} };
0297 
0298     {
0299         Transaction tr(db.get(), Transaction::ReadOnly);
0300         DBState actualState = DBState::fromTransaction(&tr);
0301         QVERIFY(DBState::debugCompare(actualState, state));
0302     }
0303 
0304     Document doc1_clone = doc1; // save state for later reset
0305 
0306     for (auto pos : {1, 3, 6}) {
0307         doc1.addPositionTerm("a", pos);
0308     }
0309     for (auto pos : {2, 4, 5}) {
0310         doc1.addPositionTerm("abc", pos);
0311     }
0312     for (auto pos : {12, 14, 15}) {
0313         doc1.addPositionTerm("dab", pos);
0314     }
0315     for (auto pos : {11, 12}) {
0316         doc3.addPositionTerm("dab", pos);
0317     }
0318     state.positionDb["a"] = {PositionInfo(id1, {1, 3, 6})};
0319     state.positionDb["abc"] = {PositionInfo(id1, {2, 4, 5})};
0320     if (id1 < id3) {
0321         state.positionDb["dab"] = {PositionInfo(id1, {12, 14, 15}), PositionInfo(id3, {11, 12})};
0322     } else {
0323         state.positionDb["dab"] = {PositionInfo(id3, {11, 12}), PositionInfo(id1, {12, 14, 15})};
0324     }
0325 
0326     {
0327         Transaction tr(db.get(), Transaction::ReadWrite);
0328         tr.replaceDocument(doc1, DocumentOperation::Everything);
0329         tr.replaceDocument(doc3, DocumentOperation::Everything);
0330         tr.commit();
0331     }
0332     {
0333         Transaction tr(db.get(), Transaction::ReadOnly);
0334         DBState actualState = DBState::fromTransaction(&tr);
0335         QVERIFY(DBState::debugCompare(actualState, state));
0336     }
0337 
0338     for (auto pos : {11, 12}) { // extend
0339         doc1.addPositionTerm("abc", pos);
0340     }
0341     for (auto pos : {16, 17}) { // extend, make sure positions for doc3 are kept
0342         doc1.addPositionTerm("dab", pos);
0343     }
0344     for (auto pos : {7, 8, 9}) { // add positions
0345         doc2.addPositionTerm("a", pos);
0346     }
0347     for (auto pos : {7, 8, 9}) { // add new term with positions
0348         doc2.addPositionTerm("abc", pos);
0349     }
0350 
0351     Document doc2_clone = doc2; // save state for later reset
0352     doc2.addPositionTerm("abcd", 500); // add position for existing term
0353 
0354     state.postingDb = {{"a", {id1, id2}}, {"abc", {id1, id2}}, {"abcd", {id2}}, {"dab", {id1, id3}}, {"file1", {id1}}, {"file2", {id2}}, {"file3", {id3}} };
0355     state.docTermsDb = {{id1, {"a", "abc", "dab"}}, {id2, {"a", "abc", "abcd"}}, {id3, {"dab"}} };
0356     if (id1 < id2) {
0357         state.positionDb["a"] = {PositionInfo(id1, {1, 3, 6}), PositionInfo(id2, {7, 8, 9})};
0358         state.positionDb["abc"] = {PositionInfo(id1, {2, 4, 5, 11, 12}), PositionInfo(id2, {7, 8, 9})};
0359     } else {
0360         state.positionDb["a"] = {PositionInfo(id2, {7, 8, 9}), PositionInfo(id1, {1, 3, 6})};
0361         state.positionDb["abc"] = {PositionInfo(id2, {7, 8, 9}), PositionInfo(id1, {2, 4, 5, 11, 12})};
0362     }
0363     if (id1 < id3) {
0364         state.positionDb["dab"] = {PositionInfo(id1, {12, 14, 15, 16, 17}), PositionInfo(id3, {11, 12})};
0365     } else {
0366         state.positionDb["dab"] = {PositionInfo(id3, {11, 12}), PositionInfo(id1, {12, 14, 15, 16, 17})};
0367     }
0368     state.positionDb["abcd"] = {PositionInfo(id2, {500})};
0369 
0370     {
0371         Transaction tr(db.get(), Transaction::ReadWrite);
0372         tr.replaceDocument(doc1, DocumentOperation::Everything);
0373         tr.replaceDocument(doc2, DocumentOperation::Everything);
0374         tr.commit();
0375     }
0376     {
0377         Transaction tr(db.get(), Transaction::ReadOnly);
0378         DBState actualState = DBState::fromTransaction(&tr);
0379         QVERIFY(DBState::debugCompare(actualState, state));
0380     }
0381 
0382     // Reset some positions of doc1
0383     for (auto pos : {2, 4, 5, 11, 12}) { // keep "abc"
0384         doc1_clone.addPositionTerm("abc", pos);
0385     }
0386     for (auto pos : {12, 14, 17}) { // remove 15, 16 from dab
0387         doc1_clone.addPositionTerm("dab", pos);
0388     }
0389     state.positionDb["a"] = {PositionInfo(id2, {7, 8, 9})}; // doc1 removed
0390     state.positionDb.remove("abcd"); // positions for abcd removed
0391     if (id1 < id3) {
0392         state.positionDb["dab"] = {PositionInfo(id1, {12, 14, 17}), PositionInfo(id3, {11, 12})};
0393     } else {
0394         state.positionDb["dab"] = {PositionInfo(id3, {11, 12}), PositionInfo(id1, {12, 14, 17})};
0395     }
0396 
0397     {
0398         Transaction tr(db.get(), Transaction::ReadWrite);
0399         tr.replaceDocument(doc1_clone, DocumentOperation::Everything);
0400         tr.replaceDocument(doc2_clone, DocumentOperation::Everything);
0401         tr.commit();
0402     }
0403     {
0404         Transaction tr(db.get(), Transaction::ReadOnly);
0405         DBState actualState = DBState::fromTransaction(&tr);
0406         QVERIFY(DBState::debugCompare(actualState, state));
0407     }
0408 }
0409 
0410 void WriteTransactionTest::testIdempotentDocumentChange()
0411 {
0412     const QString url1(dir->path() + QStringLiteral("/file1"));
0413 
0414     Document doc1 = createDocument(url1, 5, 1, {"a", "abc", "dab"}, {"file1"}, {}, m_dirId);
0415     Document doc2 = createDocument(url1, 5, 1, {"a", "abcd", "dab"}, {"file1"}, {}, m_dirId);
0416     quint64 id = doc1.id();
0417     doc2.setId(doc1.id());
0418 
0419     {
0420         Transaction tr(db.get(), Transaction::ReadWrite);
0421         tr.addDocument(doc1);
0422         tr.commit();
0423     }
0424 
0425     DBState state;
0426     state.postingDb = {{"a", {id}}, {"abc", {id}}, {"dab", {id}}, {"file1", {id}} };
0427     state.positionDb = {};
0428     state.docTermsDb = {{id, {"a", "abc", "dab"} }};
0429     state.docFileNameTermsDb = {{id, {"file1"} }};
0430     state.docXAttrTermsDb = {};
0431     state.docTimeDb = {{id, DocumentTimeDB::TimeInfo(5, 1)}};
0432     state.mtimeDb = {{5, id}};
0433 
0434     {
0435         Transaction tr(db.get(), Transaction::ReadOnly);
0436         DBState actualState = DBState::fromTransaction(&tr);
0437         QVERIFY(DBState::debugCompare(actualState, state));
0438     }
0439 
0440     {
0441         Transaction tr(db.get(), Transaction::ReadWrite);
0442         tr.replaceDocument(doc2, DocumentOperation::Everything);
0443         tr.replaceDocument(doc2, DocumentOperation::Everything);
0444         tr.commit();
0445     }
0446 
0447     state.postingDb = {{"a", {id}}, {"abcd", {id}}, {"dab", {id}}, {"file1", {id}} };
0448     state.docTermsDb = {{id, {"a", "abcd", "dab"} }};
0449 
0450     {
0451         Transaction tr(db.get(), Transaction::ReadOnly);
0452         DBState actualState = DBState::fromTransaction(&tr);
0453         QVERIFY(DBState::debugCompare(actualState, state));
0454     }
0455 }
0456 
0457 void WriteTransactionTest::testTransactionReset()
0458 {
0459     const QString url1(dir->path() + QStringLiteral("/file1"));
0460 
0461     Document doc1 = createDocument(url1, 0, 1, {"a", "abc", "dab"}, {"file1"}, {}, m_dirId);
0462 
0463     {
0464         Transaction tr(db.get(), Transaction::ReadWrite);
0465         tr.addDocument(doc1);
0466         tr.commit();
0467     }
0468 
0469     Transaction tr(db.get(), Transaction::ReadWrite);
0470     for (int i = 0; i < 3; i++) {
0471         doc1.setMTime(i);
0472         tr.replaceDocument(doc1, DocumentOperation::DocumentTime);
0473         if (i % 2) {
0474             tr.commit();
0475             tr.reset(Transaction::ReadWrite);
0476         }
0477     }
0478     tr.commit();
0479 }
0480 
0481 QTEST_MAIN(WriteTransactionTest)
0482 
0483 #include "writetransactiontest.moc"