1 /*
2 This file is part of the KDE Baloo project.
3 SPDX-FileCopyrightText: 2015 Vishesh Handa <vhanda@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.1-or-later
6 */
7
8 #include "writetransaction.h"
9 #include "dbstate.h"
10 #include "database.h"
11 #include "idutils.h"
12
13 #include <QTest>
14 #include <QTemporaryDir>
15
16 using namespace Baloo;
17
18 class WriteTransactionTest : public QObject
19 {
20 Q_OBJECT
21 private Q_SLOTS:
init()22 void init() {
23 dir = new QTemporaryDir();
24 db = new Database(dir->path());
25 db->open(Database::CreateDatabase);
26 }
27
cleanup()28 void cleanup() {
29 delete db;
30 delete dir;
31 }
32
33 void testAddDocument();
34 void testAddDocumentTwoDocuments();
35 void testAddAndRemoveOneDocument();
36 void testAddAndReplaceOneDocument();
37 void testIdempotentDocumentChange();
38
39 void testRemoveRecursively();
40 void testDocumentId();
41 void testTermPositions();
42 private:
43 QTemporaryDir* dir;
44 Database* db;
45 };
46
touchFile(const QString & path)47 static void touchFile(const QString& path) {
48 QFile file(path);
49 file.open(QIODevice::WriteOnly);
50 file.write("data");
51 file.close();
52 }
53
testAddDocument()54 void WriteTransactionTest::testAddDocument()
55 {
56 Transaction tr(db, Transaction::ReadWrite);
57
58 const QString filePath(dir->path() + QStringLiteral("/file"));
59 touchFile(filePath);
60 QByteArray url = QFile::encodeName(filePath);
61 quint64 id = filePathToId(url);
62
63 QCOMPARE(tr.hasDocument(id), false);
64
65 Document doc;
66 doc.setId(id);
67 doc.setUrl(url);
68 doc.addTerm("a");
69 doc.addTerm("ab");
70 doc.addTerm("abc");
71 doc.addTerm("power");
72 doc.addFileNameTerm("link");
73 doc.addXattrTerm("system");
74 doc.setMTime(1);
75 doc.setCTime(2);
76
77 tr.addDocument(doc);
78 tr.commit();
79
80 Transaction tr2(db, Transaction::ReadOnly);
81
82 DBState state;
83 state.postingDb = {{"a", {id}}, {"ab", {id}}, {"abc", {id}}, {"power", {id}}, {"system", {id}}, {"link", {id}}};
84 state.positionDb = {};
85 state.docTermsDb = {{id, {"a", "ab", "abc", "power"} }};
86 state.docFileNameTermsDb = {{id, {"link"} }};
87 state.docXAttrTermsDb = {{id, {"system"} }};
88 state.docTimeDb = {{id, DocumentTimeDB::TimeInfo(1, 2)}};
89 state.mtimeDb = {{1, id}};
90
91 DBState actualState = DBState::fromTransaction(&tr2);
92 QCOMPARE(actualState, state);
93 }
94
95
createDocument(const QString & filePath,quint32 mtime,quint32 ctime,const QVector<QByteArray> & terms,const QVector<QByteArray> & fileNameTerms,const QVector<QByteArray> & xattrTerms)96 static Document createDocument(const QString& filePath, quint32 mtime, quint32 ctime, const QVector<QByteArray>& terms,
97 const QVector<QByteArray>& fileNameTerms, const QVector<QByteArray>& xattrTerms)
98 {
99 Document doc;
100
101 const QByteArray url = QFile::encodeName(filePath);
102 doc.setId(filePathToId(url));
103 doc.setUrl(url);
104
105 for (const QByteArray& term: terms) {
106 doc.addTerm(term);
107 }
108 for (const QByteArray& term: fileNameTerms) {
109 doc.addFileNameTerm(term);
110 }
111 for (const QByteArray& term: xattrTerms) {
112 doc.addXattrTerm(term);
113 }
114 doc.setMTime(mtime);
115 doc.setCTime(ctime);
116
117 return doc;
118 }
119
testAddDocumentTwoDocuments()120 void WriteTransactionTest::testAddDocumentTwoDocuments()
121 {
122 const QString url1(dir->path() + QStringLiteral("/file1"));
123 const QString url2(dir->path() + QStringLiteral("/file2"));
124 touchFile(url1);
125 touchFile(url2);
126
127 Document doc1 = createDocument(url1, 5, 1, {"a", "abc", "dab"}, {"file1"}, {});
128 Document doc2 = createDocument(url2, 6, 2, {"a", "abcd", "dab"}, {"file2"}, {});
129
130 {
131 Transaction tr(db, Transaction::ReadWrite);
132 tr.addDocument(doc1);
133 tr.addDocument(doc2);
134 tr.commit();
135 }
136
137 Transaction tr(db, Transaction::ReadOnly);
138
139 quint64 id1 = doc1.id();
140 quint64 id2 = doc2.id();
141
142 DBState state;
143 state.postingDb = {{"a", {id1, id2}}, {"abc", {id1}}, {"abcd", {id2}}, {"dab", {id1, id2}}, {"file1", {id1}}, {"file2", {id2}}};
144 state.positionDb = {};
145 state.docTermsDb = {{id1, {"a", "abc", "dab"}}, {id2, {"a", "abcd", "dab"}}};
146 state.docFileNameTermsDb = {{id1, {"file1"}}, {id2, {"file2"}}};
147 state.docXAttrTermsDb = {};
148 state.docTimeDb = {{id1, DocumentTimeDB::TimeInfo(5, 1)}, {id2, DocumentTimeDB::TimeInfo(6, 2)}};
149 state.mtimeDb = {{5, id1}, {6, id2}};
150
151 DBState actualState = DBState::fromTransaction(&tr);
152 QVERIFY(DBState::debugCompare(actualState, state));
153 }
154
testAddAndRemoveOneDocument()155 void WriteTransactionTest::testAddAndRemoveOneDocument()
156 {
157 const QString url1(dir->path() + QStringLiteral("/file1"));
158 touchFile(url1);
159
160 Document doc1 = createDocument(url1, 5, 1, {"a", "abc", "dab"}, {"file1"}, {});
161
162 {
163 Transaction tr(db, Transaction::ReadWrite);
164 tr.addDocument(doc1);
165 tr.commit();
166 }
167 {
168 Transaction tr(db, Transaction::ReadWrite);
169 tr.removeDocument(doc1.id());
170 tr.commit();
171 }
172
173 Transaction tr(db, Transaction::ReadOnly);
174 DBState actualState = DBState::fromTransaction(&tr);
175 QVERIFY(DBState::debugCompare(actualState, DBState()));
176 }
177
testAddAndReplaceOneDocument()178 void WriteTransactionTest::testAddAndReplaceOneDocument()
179 {
180 const QString url1(dir->path() + QStringLiteral("/file1"));
181 touchFile(url1);
182
183 Document doc1 = createDocument(url1, 5, 1, {"a", "abc", "dab"}, {"file1"}, {});
184 Document doc2 = createDocument(url1, 6, 2, {"a", "abc", "xxx"}, {"file1", "yyy"}, {});
185 quint64 id = doc1.id();
186 doc2.setId(id);
187
188 {
189 Transaction tr(db, Transaction::ReadWrite);
190 tr.addDocument(doc1);
191 tr.commit();
192 }
193
194 DBState state;
195 state.postingDb = {{"a", {id}}, {"abc", {id}}, {"dab", {id}}, {"file1", {id}} };
196 state.positionDb = {};
197 state.docTermsDb = {{id, {"a", "abc", "dab"} }};
198 state.docFileNameTermsDb = {{id, {"file1"} }};
199 state.docXAttrTermsDb = {};
200 state.docTimeDb = {{id, DocumentTimeDB::TimeInfo(5, 1)}};
201 state.mtimeDb = {{5, id}};
202
203 {
204 Transaction tr(db, Transaction::ReadOnly);
205 DBState actualState = DBState::fromTransaction(&tr);
206 QVERIFY(DBState::debugCompare(actualState, state));
207 }
208
209 {
210 Transaction tr(db, Transaction::ReadWrite);
211 tr.replaceDocument(doc2, DocumentOperation::Everything);
212 tr.commit();
213 }
214
215 state.postingDb = {{"a", {id}}, {"abc", {id}}, {"xxx", {id}}, {"file1", {id}}, {"yyy", {id}} };
216 state.docTermsDb = {{id, {"a", "abc", "xxx"} }};
217 state.docFileNameTermsDb = {{id, {"file1", "yyy"} }};
218 state.docTimeDb = {{id, DocumentTimeDB::TimeInfo(6, 2)}};
219 state.mtimeDb = {{6, id}};
220
221 Transaction tr(db, Transaction::ReadOnly);
222 DBState actualState = DBState::fromTransaction(&tr);
223 QVERIFY(DBState::debugCompare(actualState, state));
224 }
225
testRemoveRecursively()226 void WriteTransactionTest::testRemoveRecursively()
227 {
228 const QString path = dir->path();
229 const QString url1(path + QStringLiteral("/file1"));
230 const QString dirPath(path + QStringLiteral("/dir"));
231 const QString url2(dirPath + QStringLiteral("/file1"));
232
233 touchFile(url1);
234 QDir().mkpath(dirPath);
235 touchFile(url2);
236
237 Document doc1 = createDocument(url1, 5, 1, {"a", "abc", "dab"}, {"file1"}, {});
238 Document doc2 = createDocument(url2, 5, 1, {"a", "abc", "dab"}, {"file1"}, {});
239 Document doc3 = createDocument(dirPath, 5, 1, {"a"}, {"dir"}, {});
240
241 {
242 Transaction tr(db, Transaction::ReadWrite);
243 tr.addDocument(doc1);
244 tr.addDocument(doc2);
245 tr.addDocument(doc3);
246 tr.commit();
247 }
248 {
249 Transaction tr(db, Transaction::ReadWrite);
250 tr.removeRecursively(filePathToId(dir->path().toUtf8()));
251 tr.commit();
252 }
253
254 Transaction tr(db, Transaction::ReadOnly);
255 DBState actualState = DBState::fromTransaction(&tr);
256 QVERIFY(DBState::debugCompare(actualState, DBState()));
257 }
258
testDocumentId()259 void WriteTransactionTest::testDocumentId()
260 {
261 const QString url1(dir->path() + QStringLiteral("/file1"));
262 touchFile(url1);
263
264 Document doc1 = createDocument(url1, 5, 1, {"a", "abc", "dab"}, {"file1"}, {});
265
266 {
267 Transaction tr(db, Transaction::ReadWrite);
268 tr.addDocument(doc1);
269 tr.commit();
270 }
271
272 Transaction tr(db, Transaction::ReadOnly);
273 QCOMPARE(tr.documentId(QFile::encodeName(url1)), doc1.id());
274 }
275
testTermPositions()276 void WriteTransactionTest::testTermPositions()
277 {
278 const QString url1(dir->path() + QStringLiteral("/file1"));
279 const QString url2(dir->path() + QStringLiteral("/file2"));
280 const QString url3(dir->path() + QStringLiteral("/file3"));
281 touchFile(url1);
282 touchFile(url2);
283 touchFile(url3);
284
285 Document doc1 = createDocument(url1, 5, 1, {"a", "abc", "dab"}, {"file1"}, {});
286 quint64 id1 = doc1.id();
287 Document doc2 = createDocument(url2, 5, 2, {"a", "abcd"}, {"file2"}, {});
288 quint64 id2 = doc2.id();
289 Document doc3 = createDocument(url3, 6, 3, {"dab"}, {"file3"}, {});
290 quint64 id3 = doc3.id();
291
292 {
293 Transaction tr(db, Transaction::ReadWrite);
294 tr.addDocument(doc1);
295 tr.addDocument(doc2);
296 tr.addDocument(doc3);
297 tr.commit();
298 }
299
300 DBState state;
301 state.postingDb = {{"a", {id1, id2}}, {"abc", {id1}}, {"abcd", {id2}}, {"dab", {id1, id3}}, {"file1", {id1}}, {"file2", {id2}}, {"file3", {id3}} };
302 state.positionDb = {};
303 state.docTermsDb = {{id1, {"a", "abc", "dab"}}, {id2, {"a", "abcd"}}, {id3, {"dab"}} };
304 state.docFileNameTermsDb = {{id1, {"file1"}}, {id2, {"file2"}}, {id3, {"file3"}} };
305 state.docXAttrTermsDb = {};
306 state.docTimeDb = {{id1, DocumentTimeDB::TimeInfo(5, 1)}, {id2, DocumentTimeDB::TimeInfo(5, 2)}, {id3, DocumentTimeDB::TimeInfo(6, 3)} };
307 state.mtimeDb = {{5, id1}, {5, id2}, {6, id3} };
308
309 {
310 Transaction tr(db, Transaction::ReadOnly);
311 DBState actualState = DBState::fromTransaction(&tr);
312 QVERIFY(DBState::debugCompare(actualState, state));
313 }
314
315 Document doc1_clone = doc1; // save state for later reset
316
317 for (auto pos : {1, 3, 6}) {
318 doc1.addPositionTerm("a", pos);
319 }
320 for (auto pos : {2, 4, 5}) {
321 doc1.addPositionTerm("abc", pos);
322 }
323 for (auto pos : {12, 14, 15}) {
324 doc1.addPositionTerm("dab", pos);
325 }
326 for (auto pos : {11, 12}) {
327 doc3.addPositionTerm("dab", pos);
328 }
329 state.positionDb["a"] = {PositionInfo(id1, {1, 3, 6})};
330 state.positionDb["abc"] = {PositionInfo(id1, {2, 4, 5})};
331 if (id1 < id3) {
332 state.positionDb["dab"] = {PositionInfo(id1, {12, 14, 15}), PositionInfo(id3, {11, 12})};
333 } else {
334 state.positionDb["dab"] = {PositionInfo(id3, {11, 12}), PositionInfo(id1, {12, 14, 15})};
335 }
336
337 {
338 Transaction tr(db, Transaction::ReadWrite);
339 tr.replaceDocument(doc1, DocumentOperation::Everything);
340 tr.replaceDocument(doc3, DocumentOperation::Everything);
341 tr.commit();
342 }
343 {
344 Transaction tr(db, Transaction::ReadOnly);
345 DBState actualState = DBState::fromTransaction(&tr);
346 QVERIFY(DBState::debugCompare(actualState, state));
347 }
348
349 for (auto pos : {11, 12}) { // extend
350 doc1.addPositionTerm("abc", pos);
351 }
352 for (auto pos : {16, 17}) { // extend, make sure positions for doc3 are kept
353 doc1.addPositionTerm("dab", pos);
354 }
355 for (auto pos : {7, 8, 9}) { // add positions
356 doc2.addPositionTerm("a", pos);
357 }
358 for (auto pos : {7, 8, 9}) { // add new term with positions
359 doc2.addPositionTerm("abc", pos);
360 }
361
362 Document doc2_clone = doc2; // save state for later reset
363 doc2.addPositionTerm("abcd", 500); // add position for existing term
364
365 state.postingDb = {{"a", {id1, id2}}, {"abc", {id1, id2}}, {"abcd", {id2}}, {"dab", {id1, id3}}, {"file1", {id1}}, {"file2", {id2}}, {"file3", {id3}} };
366 state.docTermsDb = {{id1, {"a", "abc", "dab"}}, {id2, {"a", "abc", "abcd"}}, {id3, {"dab"}} };
367 if (id1 < id2) {
368 state.positionDb["a"] = {PositionInfo(id1, {1, 3, 6}), PositionInfo(id2, {7, 8, 9})};
369 state.positionDb["abc"] = {PositionInfo(id1, {2, 4, 5, 11, 12}), PositionInfo(id2, {7, 8, 9})};
370 } else {
371 state.positionDb["a"] = {PositionInfo(id2, {7, 8, 9}), PositionInfo(id1, {1, 3, 6})};
372 state.positionDb["abc"] = {PositionInfo(id2, {7, 8, 9}), PositionInfo(id1, {2, 4, 5, 11, 12})};
373 }
374 if (id1 < id3) {
375 state.positionDb["dab"] = {PositionInfo(id1, {12, 14, 15, 16, 17}), PositionInfo(id3, {11, 12})};
376 } else {
377 state.positionDb["dab"] = {PositionInfo(id3, {11, 12}), PositionInfo(id1, {12, 14, 15, 16, 17})};
378 }
379 state.positionDb["abcd"] = {PositionInfo(id2, {500})};
380
381 {
382 Transaction tr(db, Transaction::ReadWrite);
383 tr.replaceDocument(doc1, DocumentOperation::Everything);
384 tr.replaceDocument(doc2, DocumentOperation::Everything);
385 tr.commit();
386 }
387 {
388 Transaction tr(db, Transaction::ReadOnly);
389 DBState actualState = DBState::fromTransaction(&tr);
390 QVERIFY(DBState::debugCompare(actualState, state));
391 }
392
393 // Reset some positions of doc1
394 for (auto pos : {2, 4, 5, 11, 12}) { // keep "abc"
395 doc1_clone.addPositionTerm("abc", pos);
396 }
397 for (auto pos : {12, 14, 17}) { // remove 15, 16 from dab
398 doc1_clone.addPositionTerm("dab", pos);
399 }
400 state.positionDb["a"] = {PositionInfo(id2, {7, 8, 9})}; // doc1 removed
401 state.positionDb.remove("abcd"); // positions for abcd removed
402 if (id1 < id3) {
403 state.positionDb["dab"] = {PositionInfo(id1, {12, 14, 17}), PositionInfo(id3, {11, 12})};
404 } else {
405 state.positionDb["dab"] = {PositionInfo(id3, {11, 12}), PositionInfo(id1, {12, 14, 17})};
406 }
407
408 {
409 Transaction tr(db, Transaction::ReadWrite);
410 tr.replaceDocument(doc1_clone, DocumentOperation::Everything);
411 tr.replaceDocument(doc2_clone, DocumentOperation::Everything);
412 tr.commit();
413 }
414 {
415 Transaction tr(db, Transaction::ReadOnly);
416 DBState actualState = DBState::fromTransaction(&tr);
417 QVERIFY(DBState::debugCompare(actualState, state));
418 }
419 }
420
testIdempotentDocumentChange()421 void WriteTransactionTest::testIdempotentDocumentChange()
422 {
423 const QString url1(dir->path() + QStringLiteral("/file1"));
424 touchFile(url1);
425
426 Document doc1 = createDocument(url1, 5, 1, {"a", "abc", "dab"}, {"file1"}, {});
427 Document doc2 = createDocument(url1, 5, 1, {"a", "abcd", "dab"}, {"file1"}, {});
428 quint64 id = doc1.id();
429
430 {
431 Transaction tr(db, Transaction::ReadWrite);
432 tr.addDocument(doc1);
433 tr.commit();
434 }
435
436 DBState state;
437 state.postingDb = {{"a", {id}}, {"abc", {id}}, {"dab", {id}}, {"file1", {id}} };
438 state.positionDb = {};
439 state.docTermsDb = {{id, {"a", "abc", "dab"} }};
440 state.docFileNameTermsDb = {{id, {"file1"} }};
441 state.docXAttrTermsDb = {};
442 state.docTimeDb = {{id, DocumentTimeDB::TimeInfo(5, 1)}};
443 state.mtimeDb = {{5, id}};
444
445 {
446 Transaction tr(db, Transaction::ReadOnly);
447 DBState actualState = DBState::fromTransaction(&tr);
448 QVERIFY(DBState::debugCompare(actualState, state));
449 }
450
451 {
452 Transaction tr(db, Transaction::ReadWrite);
453 tr.replaceDocument(doc2, DocumentOperation::Everything);
454 tr.replaceDocument(doc2, DocumentOperation::Everything);
455 tr.commit();
456 }
457
458 state.postingDb = {{"a", {id}}, {"abcd", {id}}, {"dab", {id}}, {"file1", {id}} };
459 state.docTermsDb = {{id, {"a", "abcd", "dab"} }};
460
461 {
462 Transaction tr(db, Transaction::ReadOnly);
463 DBState actualState = DBState::fromTransaction(&tr);
464 QVERIFY(DBState::debugCompare(actualState, state));
465 }
466
467 }
468
469 QTEST_MAIN(WriteTransactionTest)
470
471 #include "writetransactiontest.moc"
472