Branch data Line data Source code
1 : : /** @file flint_compact.cc
2 : : * @brief Compact a flint database, or merge and compact several.
3 : : */
4 : : /* Copyright (C) 2004,2005,2006,2007,2008,2009,2010 Olly Betts
5 : : *
6 : : * This program is free software; you can redistribute it and/or
7 : : * modify it under the terms of the GNU General Public License as
8 : : * published by the Free Software Foundation; either version 2 of the
9 : : * License, or (at your option) any later version.
10 : : *
11 : : * This program is distributed in the hope that it will be useful,
12 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : : * GNU General Public License for more details.
15 : : *
16 : : * You should have received a copy of the GNU General Public License
17 : : * along with this program; if not, write to the Free Software
18 : : * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
19 : : * USA
20 : : */
21 : :
22 : : #include <config.h>
23 : :
24 : : #include <xapian/compactor.h>
25 : :
26 : : #include <algorithm>
27 : : #include <queue>
28 : :
29 : : #include <cstdio>
30 : :
31 : : #include "safeerrno.h"
32 : : #include <sys/types.h>
33 : : #include "safesysstat.h"
34 : :
35 : : #include "flint_table.h"
36 : : #include "flint_compact.h"
37 : : #include "flint_cursor.h"
38 : : #include "flint_utils.h"
39 : : #include "internaltypes.h"
40 : : #include "utils.h"
41 : :
42 : : #include "../byte_length_strings.h"
43 : : #include "../prefix_compressed_strings.h"
44 : : #include <xapian.h>
45 : :
46 : : using namespace std;
47 : :
48 : : // Put all the helpers in a namespace to avoid symbols colliding with those of
49 : : // the same name in chert_compact.cc.
50 : : namespace FlintCompact {
51 : :
52 : : static inline bool
53 : 981 : is_metainfo_key(const string & key)
54 : : {
55 [ + + ][ + - ]: 981 : return key.size() == 1 && key[0] == '\0';
56 : : }
57 : :
58 : : static inline bool
59 : 948 : is_user_metadata_key(const string & key)
60 : : {
61 [ + - ][ - + ]: 948 : return key.size() > 1 && key[0] == '\0' && key[1] == '\xc0';
[ # # ]
62 : : }
63 : :
64 : : class PostlistCursor : private FlintCursor {
65 : : Xapian::docid offset;
66 : :
67 : : public:
68 : : string key, tag;
69 : : Xapian::docid firstdid;
70 : : Xapian::termcount tf, cf;
71 : :
72 : 22 : PostlistCursor(FlintTable *in, Xapian::docid offset_)
73 : 22 : : FlintCursor(in), offset(offset_), firstdid(0)
74 : : {
75 : 22 : find_entry(string());
76 : 22 : next();
77 : 22 : }
78 : :
79 : 22 : ~PostlistCursor()
80 : : {
81 [ + - ]: 22 : delete FlintCursor::get_table();
82 : 22 : }
83 : :
84 : 981 : bool next() {
85 [ + + ]: 981 : if (!FlintCursor::next()) return false;
86 : : // We put all chunks into the non-initial chunk form here, then fix up
87 : : // the first chunk for each term in the merged database as we merge.
88 : 959 : read_tag();
89 : 959 : key = current_key;
90 : 959 : tag = current_tag;
91 : 959 : tf = cf = 0;
92 [ + + ]: 959 : if (is_metainfo_key(key)) return true;
93 [ - + ]: 937 : if (is_user_metadata_key(key)) return true;
94 : : // Adjust key if this is *NOT* an initial chunk.
95 : : // key is: F_pack_string_preserving_sort(tname)
96 : : // plus optionally: F_pack_uint_preserving_sort(did)
97 : 937 : const char * d = key.data();
98 : 937 : const char * e = d + key.size();
99 : 937 : string tname;
100 [ - + ]: 937 : if (!F_unpack_string_preserving_sort(&d, e, tname))
101 : 0 : throw Xapian::DatabaseCorruptError("Bad postlist key");
102 [ + + ]: 937 : if (d == e) {
103 : : // This is an initial chunk for a term, so adjust tag header.
104 : 923 : d = tag.data();
105 : 923 : e = d + tag.size();
106 [ + - + - ]: 923 : if (!F_unpack_uint(&d, e, &tf) ||
[ - + ][ - + ]
107 : : !F_unpack_uint(&d, e, &cf) ||
108 : : !F_unpack_uint(&d, e, &firstdid)) {
109 : 0 : throw Xapian::DatabaseCorruptError("Bad postlist tag");
110 : : }
111 : 923 : ++firstdid;
112 : 923 : tag.erase(0, d - tag.data());
113 : : } else {
114 : : // Not an initial chunk, so adjust key.
115 : 14 : size_t tmp = d - key.data();
116 [ + - - + ]: 14 : if (!F_unpack_uint_preserving_sort(&d, e, &firstdid) || d != e)
[ - + ]
117 : 0 : throw Xapian::DatabaseCorruptError("Bad postlist key");
118 : 14 : key.erase(tmp);
119 : : }
120 : 937 : firstdid += offset;
121 : 981 : return true;
122 : : }
123 : : };
124 : :
125 : : class PostlistCursorGt {
126 : : public:
127 : : /** Return true if and only if a's key is strictly greater than b's key.
128 : : */
129 : 951 : bool operator()(const PostlistCursor *a, const PostlistCursor *b) {
130 [ + + ]: 951 : if (a->key > b->key) return true;
131 [ + + ]: 563 : if (a->key != b->key) return false;
132 : 951 : return (a->firstdid > b->firstdid);
133 : : }
134 : : };
135 : :
136 : : static void
137 : 11 : merge_postlists(Xapian::Compactor & compactor,
138 : : FlintTable * out, vector<Xapian::docid>::const_iterator offset,
139 : : vector<string>::const_iterator b,
140 : : vector<string>::const_iterator e,
141 : : Xapian::docid last_docid)
142 : : {
143 : 11 : totlen_t tot_totlen = 0;
144 : 11 : priority_queue<PostlistCursor *, vector<PostlistCursor *>, PostlistCursorGt> pq;
145 [ + + ]: 33 : for ( ; b != e; ++b, ++offset) {
146 : 22 : FlintTable *in = new FlintTable("postlist", *b, true);
147 : 22 : in->open();
148 [ - + ]: 22 : if (in->empty()) {
149 : : // Skip empty tables.
150 [ # # ]: 0 : delete in;
151 : 0 : continue;
152 : : }
153 : :
154 : : // PostlistCursor takes ownership of FlintTable in and is
155 : : // responsible for deleting it.
156 : 22 : PostlistCursor * cur = new PostlistCursor(in, *offset);
157 : : // Merge the METAINFO tags from each database into one.
158 : : // They have a key consisting of a single zero byte.
159 : : // They may be absent, if the database contains no documents. If it
160 : : // has user metadata we'll still get here.
161 [ + - ]: 22 : if (is_metainfo_key(cur->key)) {
162 : 22 : const char * data = cur->tag.data();
163 : 22 : const char * end = data + cur->tag.size();
164 : 22 : Xapian::docid dummy_did = 0;
165 [ - + ]: 22 : if (!F_unpack_uint(&data, end, &dummy_did)) {
166 : 0 : throw Xapian::DatabaseCorruptError("Tag containing meta information is corrupt.");
167 : : }
168 : 22 : totlen_t totlen = 0;
169 [ - + ]: 22 : if (!F_unpack_uint_last(&data, end, &totlen)) {
170 : 0 : throw Xapian::DatabaseCorruptError("Tag containing meta information is corrupt.");
171 : : }
172 : 22 : tot_totlen += totlen;
173 [ - + ]: 22 : if (tot_totlen < totlen) {
174 : 0 : throw "totlen wrapped!";
175 : : }
176 : : }
177 [ + - ]: 22 : if (cur->next()) {
178 : 22 : pq.push(cur);
179 : : } else {
180 [ # # ]: 0 : delete cur;
181 : : }
182 : : }
183 : :
184 : : {
185 : 11 : string tag = F_pack_uint(last_docid);
186 : 11 : tag += F_pack_uint_last(tot_totlen);
187 : 11 : out->add(string(1, '\0'), tag);
188 : : }
189 : :
190 : 11 : string last_key;
191 : : {
192 : : // Merge user metadata.
193 : 11 : vector<string> tags;
194 [ + - ]: 11 : while (!pq.empty()) {
195 : 11 : PostlistCursor * cur = pq.top();
196 : 11 : const string& key = cur->key;
197 [ + - ]: 11 : if (!is_user_metadata_key(key)) break;
198 : :
199 [ # # ]: 0 : if (key != last_key) {
200 [ # # ]: 0 : if (tags.size() > 1) {
201 : : Assert(!last_key.empty());
202 : : out->add(last_key,
203 : : compactor.resolve_duplicate_metadata(last_key,
204 : : tags.size(),
205 : 0 : &tags[0]));
206 [ # # ]: 0 : } else if (tags.size() == 1) {
207 : : Assert(!last_key.empty());
208 : 0 : out->add(last_key, tags[0]);
209 : : }
210 : 0 : tags.resize(0);
211 : 0 : last_key = key;
212 : : }
213 : 0 : tags.push_back(cur->tag);
214 : :
215 : 0 : pq.pop();
216 [ # # ]: 0 : if (cur->next()) {
217 : 0 : pq.push(cur);
218 : : } else {
219 [ # # ]: 0 : delete cur;
220 : : }
221 : : }
222 [ - + ]: 11 : if (tags.size() > 1) {
223 : : Assert(!last_key.empty());
224 : : out->add(last_key,
225 : : compactor.resolve_duplicate_metadata(last_key,
226 : : tags.size(),
227 : 0 : &tags[0]));
228 [ - + ]: 11 : } else if (tags.size() == 1) {
229 : : Assert(!last_key.empty());
230 : 0 : out->add(last_key, tags[0]);
231 : 11 : }
232 : : }
233 : :
234 : 11 : Xapian::termcount tf = 0, cf = 0; // Initialise to avoid warnings.
235 : 11 : vector<pair<Xapian::docid, string> > tags;
236 : 937 : while (true) {
237 : 948 : PostlistCursor * cur = NULL;
238 [ + + ]: 948 : if (!pq.empty()) {
239 : 937 : cur = pq.top();
240 : 937 : pq.pop();
241 : : }
242 : : Assert(cur == NULL || !is_user_metadata_key(cur->key));
243 [ + + ][ + + ]: 948 : if (cur == NULL || cur->key != last_key) {
[ + + ]
244 [ + + ]: 746 : if (!tags.empty()) {
245 : 735 : string first_tag = F_pack_uint(tf);
246 : 735 : first_tag += F_pack_uint(cf);
247 : 735 : first_tag += F_pack_uint(tags[0].first - 1);
248 : 735 : string tag = tags[0].second;
249 [ + + ]: 735 : tag[0] = (tags.size() == 1) ? '1' : '0';
250 : 735 : first_tag += tag;
251 : 735 : out->add(last_key, first_tag);
252 : 735 : vector<pair<Xapian::docid, string> >::const_iterator i;
253 : 735 : i = tags.begin();
254 [ + + ]: 937 : while (++i != tags.end()) {
255 : 202 : string new_key = last_key;
256 : 202 : new_key += F_pack_uint_preserving_sort(i->first);
257 : 202 : tag = i->second;
258 [ + + ]: 202 : tag[0] = (i + 1 == tags.end()) ? '1' : '0';
259 : 202 : out->add(new_key, tag);
260 : 735 : }
261 : : }
262 : 746 : tags.clear();
263 [ + + ]: 746 : if (cur == NULL) break;
264 : 735 : tf = cf = 0;
265 : 735 : last_key = cur->key;
266 : : }
267 : 937 : tf += cur->tf;
268 : 937 : cf += cur->cf;
269 : 937 : tags.push_back(make_pair(cur->firstdid, cur->tag));
270 [ + + ]: 937 : if (cur->next()) {
271 : 915 : pq.push(cur);
272 : : } else {
273 [ + - ]: 22 : delete cur;
274 : : }
275 : 11 : }
276 : 11 : }
277 : :
278 : : struct MergeCursor : public FlintCursor {
279 : 2 : MergeCursor(FlintTable *in) : FlintCursor(in) {
280 : 2 : find_entry(string());
281 : 2 : next();
282 : 2 : }
283 : :
284 : 2 : ~MergeCursor() {
285 [ + - ]: 2 : delete FlintCursor::get_table();
286 : 2 : }
287 : : };
288 : :
289 : : struct CursorGt {
290 : : /// Return true if and only if a's key is strictly greater than b's key.
291 : 0 : bool operator()(const FlintCursor *a, const FlintCursor *b) {
292 [ # # ]: 0 : if (b->after_end()) return false;
293 [ # # ]: 0 : if (a->after_end()) return true;
294 : 0 : return (a->current_key > b->current_key);
295 : : }
296 : : };
297 : :
298 : : static void
299 : 1 : merge_spellings(FlintTable * out,
300 : : vector<string>::const_iterator b,
301 : : vector<string>::const_iterator e)
302 : : {
303 : 1 : priority_queue<MergeCursor *, vector<MergeCursor *>, CursorGt> pq;
304 [ + + ]: 3 : for ( ; b != e; ++b) {
305 : 2 : FlintTable *in = new FlintTable("spelling", *b, true, DONT_COMPRESS, true);
306 : 2 : in->open();
307 [ + + ]: 2 : if (!in->empty()) {
308 : : // The MergeCursor takes ownership of FlintTable in and is
309 : : // responsible for deleting it.
310 : 1 : pq.push(new MergeCursor(in));
311 : : } else {
312 [ + - ]: 1 : delete in;
313 : : }
314 : : }
315 : :
316 [ + + ]: 6 : while (!pq.empty()) {
317 : 5 : MergeCursor * cur = pq.top();
318 : 5 : pq.pop();
319 : :
320 : 5 : string key = cur->current_key;
321 [ - + # # ]: 5 : if (pq.empty() || pq.top()->current_key > key) {
[ + - ]
322 : : // No need to merge the tags, just copy the (possibly compressed)
323 : : // tag value.
324 : 5 : bool compressed = cur->read_tag(true);
325 : 5 : out->add(key, cur->current_tag, compressed);
326 [ + + ]: 5 : if (cur->next()) {
327 : 4 : pq.push(cur);
328 : : } else {
329 [ + - ]: 1 : delete cur;
330 : : }
331 : 5 : continue;
332 : : }
333 : :
334 : : // Merge tag values with the same key:
335 : 0 : string tag;
336 [ # # ]: 0 : if (key[0] != 'W') {
337 : : // We just want the union of words, so copy over the first instance
338 : : // and skip any identical ones.
339 : : priority_queue<PrefixCompressedStringItor *,
340 : : vector<PrefixCompressedStringItor *>,
341 : 0 : PrefixCompressedStringItorGt> pqtag;
342 : : // Stick all the MergeCursor pointers in a vector because their
343 : : // current_tag members must remain valid while we're merging their
344 : : // tags, but we need to call next() on them all afterwards.
345 : 0 : vector<MergeCursor *> vec;
346 : 0 : vec.reserve(pq.size());
347 : :
348 : 0 : while (true) {
349 : 0 : cur->read_tag();
350 : 0 : pqtag.push(new PrefixCompressedStringItor(cur->current_tag));
351 : 0 : vec.push_back(cur);
352 [ # # # # ]: 0 : if (pq.empty() || pq.top()->current_key != key) break;
[ # # ]
353 : 0 : cur = pq.top();
354 : 0 : pq.pop();
355 : : }
356 : :
357 : 0 : PrefixCompressedStringWriter wr(tag);
358 : 0 : string lastword;
359 [ # # ]: 0 : while (!pqtag.empty()) {
360 : 0 : PrefixCompressedStringItor * it = pqtag.top();
361 : 0 : string word = **it;
362 [ # # ]: 0 : if (word != lastword) {
363 : 0 : lastword = word;
364 : 0 : wr.append(lastword);
365 : : }
366 : 0 : ++*it;
367 : 0 : pqtag.pop();
368 [ # # ]: 0 : if (!it->at_end()) {
369 : 0 : pqtag.push(it);
370 : : } else {
371 [ # # ]: 0 : delete it;
372 : : }
373 : : }
374 : :
375 : 0 : vector<MergeCursor *>::const_iterator i;
376 [ # # ]: 0 : for (i = vec.begin(); i != vec.end(); ++i) {
377 : 0 : cur = *i;
378 [ # # ]: 0 : if (cur->next()) {
379 : 0 : pq.push(cur);
380 : : } else {
381 [ # # ]: 0 : delete cur;
382 : : }
383 : 0 : }
384 : : } else {
385 : : // We want to sum the frequencies from tags for the same key.
386 : 0 : Xapian::termcount tot_freq = 0;
387 : 0 : while (true) {
388 : 0 : cur->read_tag();
389 : : Xapian::termcount freq;
390 : 0 : const char * p = cur->current_tag.data();
391 : 0 : const char * end = p + cur->current_tag.size();
392 [ # # # # ]: 0 : if (!F_unpack_uint_last(&p, end, &freq) || freq == 0) {
[ # # ]
393 : 0 : throw Xapian::DatabaseCorruptError("Bad spelling word freq");
394 : : }
395 : 0 : tot_freq += freq;
396 [ # # ]: 0 : if (cur->next()) {
397 : 0 : pq.push(cur);
398 : : } else {
399 [ # # ]: 0 : delete cur;
400 : : }
401 [ # # ][ # # ]: 0 : if (pq.empty() || pq.top()->current_key != key) break;
[ # # ]
402 : 0 : cur = pq.top();
403 : 0 : pq.pop();
404 : : }
405 : 0 : tag = F_pack_uint_last(tot_freq);
406 : : }
407 : 0 : out->add(key, tag);
408 : 1 : }
409 : 1 : }
410 : :
411 : : static void
412 : 1 : merge_synonyms(FlintTable * out,
413 : : vector<string>::const_iterator b,
414 : : vector<string>::const_iterator e)
415 : : {
416 : 1 : priority_queue<MergeCursor *, vector<MergeCursor *>, CursorGt> pq;
417 [ + + ]: 3 : for ( ; b != e; ++b) {
418 : 2 : FlintTable *in = new FlintTable("synonym", *b, true, DONT_COMPRESS, true);
419 : 2 : in->open();
420 [ + + ]: 2 : if (!in->empty()) {
421 : : // The MergeCursor takes ownership of FlintTable in and is
422 : : // responsible for deleting it.
423 : 1 : pq.push(new MergeCursor(in));
424 : : } else {
425 [ + - ]: 1 : delete in;
426 : : }
427 : : }
428 : :
429 [ + + ]: 2 : while (!pq.empty()) {
430 : 1 : MergeCursor * cur = pq.top();
431 : 1 : pq.pop();
432 : :
433 : 1 : string key = cur->current_key;
434 [ - + # # ]: 1 : if (pq.empty() || pq.top()->current_key > key) {
[ + - ]
435 : : // No need to merge the tags, just copy the (possibly compressed)
436 : : // tag value.
437 : 1 : bool compressed = cur->read_tag(true);
438 : 1 : out->add(key, cur->current_tag, compressed);
439 [ - + ]: 1 : if (cur->next()) {
440 : 0 : pq.push(cur);
441 : : } else {
442 [ + - ]: 1 : delete cur;
443 : : }
444 : 1 : continue;
445 : : }
446 : :
447 : : // Merge tag values with the same key:
448 : 0 : string tag;
449 : :
450 : : // We just want the union of words, so copy over the first instance
451 : : // and skip any identical ones.
452 : : priority_queue<ByteLengthPrefixedStringItor *,
453 : : vector<ByteLengthPrefixedStringItor *>,
454 : 0 : ByteLengthPrefixedStringItorGt> pqtag;
455 : 0 : vector<MergeCursor *> vec;
456 : :
457 : 0 : while (true) {
458 : 0 : cur->read_tag();
459 : 0 : pqtag.push(new ByteLengthPrefixedStringItor(cur->current_tag));
460 : 0 : vec.push_back(cur);
461 [ # # # # ]: 0 : if (pq.empty() || pq.top()->current_key != key) break;
[ # # ]
462 : 0 : cur = pq.top();
463 : 0 : pq.pop();
464 : : }
465 : :
466 : 0 : string lastword;
467 [ # # ]: 0 : while (!pqtag.empty()) {
468 : 0 : ByteLengthPrefixedStringItor * it = pqtag.top();
469 [ # # ]: 0 : if (**it != lastword) {
470 : 0 : lastword = **it;
471 : 0 : tag += byte(lastword.size() ^ MAGIC_XOR_VALUE);
472 : 0 : tag += lastword;
473 : : }
474 : 0 : ++*it;
475 : 0 : pqtag.pop();
476 [ # # ]: 0 : if (!it->at_end()) {
477 : 0 : pqtag.push(it);
478 : : } else {
479 : 0 : delete it;
480 : : }
481 : : }
482 : :
483 : 0 : vector<MergeCursor *>::const_iterator i;
484 [ # # ]: 0 : for (i = vec.begin(); i != vec.end(); ++i) {
485 : 0 : cur = *i;
486 [ # # ]: 0 : if (cur->next()) {
487 : 0 : pq.push(cur);
488 : : } else {
489 [ # # ]: 0 : delete cur;
490 : : }
491 : : }
492 : :
493 : 0 : out->add(key, tag);
494 : 1 : }
495 : 1 : }
496 : :
497 : : static void
498 : 0 : multimerge_postlists(Xapian::Compactor & compactor,
499 : : FlintTable * out, const char * tmpdir,
500 : : Xapian::docid last_docid,
501 : : vector<string> tmp, vector<Xapian::docid> off)
502 : : {
503 : 0 : unsigned int c = 0;
504 [ # # ]: 0 : while (tmp.size() > 3) {
505 : 0 : vector<string> tmpout;
506 : 0 : tmpout.reserve(tmp.size() / 2);
507 : 0 : vector<Xapian::docid> newoff;
508 : 0 : newoff.resize(tmp.size() / 2);
509 [ # # ]: 0 : for (unsigned int i = 0, j; i < tmp.size(); i = j) {
510 : 0 : j = i + 2;
511 [ # # ]: 0 : if (j == tmp.size() - 1) ++j;
512 : :
513 : 0 : string dest = tmpdir;
514 : : char buf[64];
515 : 0 : sprintf(buf, "/tmp%u_%u.", c, i / 2);
516 : 0 : dest += buf;
517 : :
518 : : // Don't compress temporary tables, even if the final table would
519 : : // be.
520 : 0 : FlintTable tmptab("postlist", dest, false);
521 : : // Use maximum blocksize for temporary tables.
522 : 0 : tmptab.create_and_open(65536);
523 : :
524 : : merge_postlists(compactor, &tmptab, off.begin() + i,
525 : 0 : tmp.begin() + i, tmp.begin() + j, 0);
526 [ # # ]: 0 : if (c > 0) {
527 [ # # ]: 0 : for (unsigned int k = i; k < j; ++k) {
528 : 0 : unlink((tmp[k] + "DB").c_str());
529 : 0 : unlink((tmp[k] + "baseA").c_str());
530 : 0 : unlink((tmp[k] + "baseB").c_str());
531 : : }
532 : : }
533 : 0 : tmpout.push_back(dest);
534 : 0 : tmptab.flush_db();
535 : 0 : tmptab.commit(1);
536 : : }
537 : 0 : swap(tmp, tmpout);
538 : 0 : swap(off, newoff);
539 : 0 : ++c;
540 : : }
541 : : merge_postlists(compactor,
542 : 0 : out, off.begin(), tmp.begin(), tmp.end(), last_docid);
543 [ # # ]: 0 : if (c > 0) {
544 [ # # ]: 0 : for (size_t k = 0; k < tmp.size(); ++k) {
545 : 0 : unlink((tmp[k] + "DB").c_str());
546 : 0 : unlink((tmp[k] + "baseA").c_str());
547 : 0 : unlink((tmp[k] + "baseB").c_str());
548 : : }
549 : : }
550 : 0 : }
551 : :
552 : : static void
553 : 38 : merge_docid_keyed(const char * tablename,
554 : : FlintTable *out, const vector<string> & inputs,
555 : : const vector<Xapian::docid> & offset, bool lazy)
556 : : {
557 [ + + ]: 114 : for (size_t i = 0; i < inputs.size(); ++i) {
558 : 76 : Xapian::docid off = offset[i];
559 : :
560 : 76 : FlintTable in(tablename, inputs[i], true, DONT_COMPRESS, lazy);
561 : 76 : in.open();
562 [ - + ]: 76 : if (in.empty()) continue;
563 : :
564 : 76 : FlintCursor cur(&in);
565 : 76 : cur.find_entry(string());
566 : :
567 : 76 : string key;
568 [ + + ]: 31523 : while (cur.next()) {
569 : : // Adjust the key if this isn't the first database.
570 [ + + ]: 31447 : if (off) {
571 : : Xapian::docid did;
572 : 364 : const char * d = cur.current_key.data();
573 : 364 : const char * e = d + cur.current_key.size();
574 [ - + ]: 364 : if (!F_unpack_uint_preserving_sort(&d, e, &did)) {
575 : 0 : string msg = "Bad key in ";
576 : 0 : msg += inputs[i];
577 : 0 : throw Xapian::DatabaseCorruptError(msg);
578 : : }
579 : 364 : did += off;
580 : 364 : key = F_pack_uint_preserving_sort(did);
581 [ + + ]: 364 : if (d != e) {
582 : : // Copy over the termname for the position table.
583 : 319 : key.append(d, e - d);
584 : : }
585 : : } else {
586 : 31083 : key = cur.current_key;
587 : : }
588 : 31447 : bool compressed = cur.read_tag(true);
589 : 31447 : out->add(key, cur.current_tag, compressed);
590 : : }
591 : : }
592 : 38 : }
593 : :
594 : : }
595 : :
596 : : using namespace FlintCompact;
597 : :
598 : : void
599 : 11 : compact_flint(Xapian::Compactor & compactor,
600 : : const char * destdir, const vector<string> & sources,
601 : : const vector<Xapian::docid> & offset, size_t block_size,
602 : : Xapian::Compactor::compaction_level compaction, bool multipass,
603 : : Xapian::docid last_docid) {
604 : : enum table_type {
605 : : POSTLIST, RECORD, TERMLIST, POSITION, VALUE, SPELLING, SYNONYM
606 : : };
607 : : struct table_list {
608 : : // The "base name" of the table.
609 : : const char * name;
610 : : // The type.
611 : : table_type type;
612 : : // zlib compression strategy to use on tags.
613 : : int compress_strategy;
614 : : // Create tables after position lazily.
615 : : bool lazy;
616 : : };
617 : :
618 : : static const table_list tables[] = {
619 : : // name type compress_strategy lazy
620 : : { "postlist", POSTLIST, DONT_COMPRESS, false },
621 : : { "record", RECORD, Z_DEFAULT_STRATEGY, false },
622 : : { "termlist", TERMLIST, Z_DEFAULT_STRATEGY, false },
623 : : { "position", POSITION, DONT_COMPRESS, true },
624 : : { "value", VALUE, DONT_COMPRESS, true },
625 : : { "spelling", SPELLING, Z_DEFAULT_STRATEGY, true },
626 : : { "synonym", SYNONYM, Z_DEFAULT_STRATEGY, true }
627 : : };
628 : : const table_list * tables_end = tables +
629 : 11 : (sizeof(tables) / sizeof(tables[0]));
630 : :
631 [ + + ][ + + ]: 88 : for (const table_list * t = tables; t < tables_end; ++t) {
632 : : // The postlist table requires an N-way merge, adjusting the
633 : : // headers of various blocks. The spelling and synonym tables also
634 : : // need special handling. The other tables have keys sorted in
635 : : // docid order, so we can merge them by simply copying all the keys
636 : : // from each source table in turn.
637 : 77 : compactor.set_status(t->name, string());
638 : :
639 : 77 : string dest = destdir;
640 : 77 : dest += '/';
641 : 77 : dest += t->name;
642 : 77 : dest += '.';
643 : :
644 : 77 : bool output_will_exist = !t->lazy;
645 : :
646 : : // Sometimes stat can fail for benign reasons (e.g. >= 2GB file
647 : : // on certain systems).
648 : 77 : bool bad_stat = false;
649 : :
650 : 77 : off_t in_size = 0;
651 : :
652 : 77 : vector<string> inputs;
653 : 77 : inputs.reserve(sources.size());
654 : 77 : size_t inputs_present = 0;
655 [ + + ]: 231 : for (vector<string>::const_iterator src = sources.begin();
656 : : src != sources.end(); ++src) {
657 : 154 : string s(*src);
658 : 154 : s += t->name;
659 : 154 : s += '.';
660 : :
661 : : struct stat sb;
662 [ + + ]: 154 : if (stat(s + "DB", &sb) == 0) {
663 : 100 : in_size += sb.st_size / 1024;
664 : 100 : output_will_exist = true;
665 : 100 : ++inputs_present;
666 [ - + ]: 54 : } else if (errno != ENOENT) {
667 : : // We get ENOENT for an optional table.
668 : 0 : bad_stat = true;
669 : 0 : output_will_exist = true;
670 : 0 : ++inputs_present;
671 : : }
672 : 154 : inputs.push_back(s);
673 : : }
674 : :
675 [ + + ]: 77 : if (!output_will_exist) {
676 : 26 : compactor.set_status(t->name, "doesn't exist");
677 : 26 : continue;
678 : : }
679 : :
680 : 51 : FlintTable out(t->name, dest, false, t->compress_strategy, t->lazy);
681 [ + + ]: 51 : if (!t->lazy) {
682 : 33 : out.create_and_open(block_size);
683 : : } else {
684 : 18 : out.erase();
685 : 18 : out.set_block_size(block_size);
686 : : }
687 : :
688 : 51 : out.set_full_compaction(compaction != compactor.STANDARD);
689 [ - + ]: 51 : if (compaction == compactor.FULLER) out.set_max_item_size(1);
690 : :
691 [ + + + + ]: 51 : switch (t->type) {
692 : : case POSTLIST:
693 [ - + ][ # # ]: 11 : if (multipass && inputs.size() > 3) {
[ - + ]
694 : : multimerge_postlists(compactor, &out, destdir, last_docid,
695 : 0 : inputs, offset);
696 : : } else {
697 : : merge_postlists(compactor, &out, offset.begin(),
698 : : inputs.begin(), inputs.end(),
699 : 11 : last_docid);
700 : : }
701 : 11 : break;
702 : : case SPELLING:
703 : 1 : merge_spellings(&out, inputs.begin(), inputs.end());
704 : 1 : break;
705 : : case SYNONYM:
706 : 1 : merge_synonyms(&out, inputs.begin(), inputs.end());
707 : 1 : break;
708 : : default:
709 : : // Position, Record, Termlist, Value.
710 : 38 : merge_docid_keyed(t->name, &out, inputs, offset, t->lazy);
711 : : break;
712 : : }
713 : :
714 : : // Commit as revision 1.
715 : 51 : out.flush_db();
716 : 51 : out.commit(1);
717 : :
718 : 51 : off_t out_size = 0;
719 [ + - ]: 51 : if (!bad_stat) {
720 : : struct stat sb;
721 [ + - ]: 51 : if (stat(dest + "DB", &sb) == 0) {
722 : 51 : out_size = sb.st_size / 1024;
723 : : } else {
724 : 0 : bad_stat = (errno != ENOENT);
725 : : }
726 : : }
727 [ - + ]: 51 : if (bad_stat) {
728 : 0 : compactor.set_status(t->name, "Done (couldn't stat all the DB files)");
729 : : } else {
730 : 51 : string status;
731 [ + + ]: 51 : if (out_size == in_size) {
732 : 12 : status = "Size unchanged (";
733 : : } else {
734 : : off_t delta;
735 [ + + ]: 39 : if (out_size < in_size) {
736 : 3 : delta = in_size - out_size;
737 : 3 : status = "Reduced by ";
738 : : } else {
739 : 36 : delta = out_size - in_size;
740 : 36 : status = "INCREASED by ";
741 : : }
742 : 39 : status += str(100 * delta / in_size);
743 : 39 : status += "% ";
744 : 39 : status += str(delta);
745 : 39 : status += "K (";
746 : 39 : status += str(in_size);
747 : 39 : status += "K -> ";
748 : : }
749 : 51 : status += str(out_size);
750 : 51 : status += "K)";
751 : 51 : compactor.set_status(t->name, status);
752 : : }
753 : : }
754 : 11 : }
|