Branch data Line data Source code
1 : : /** @file xapian-check-brass.cc
2 : : * @brief Check consistency of a brass table.
3 : : */
4 : : /* Copyright 1999,2000,2001 BrightStation PLC
5 : : * Copyright 2002,2003,2004,2005,2006,2007,2008,2009,2010 Olly Betts
6 : : *
7 : : * This program is free software; you can redistribute it and/or
8 : : * modify it under the terms of the GNU General Public License as
9 : : * published by the Free Software Foundation; either version 2 of the
10 : : * License, or (at your option) any later version.
11 : : *
12 : : * This program is distributed in the hope that it will be useful,
13 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 : : * GNU General Public License for more details.
16 : : *
17 : : * You should have received a copy of the GNU General Public License
18 : : * along with this program; if not, write to the Free Software
19 : : * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
20 : : * USA
21 : : */
22 : :
23 : : #include <config.h>
24 : :
25 : : #include "xapian-check-brass.h"
26 : :
27 : : #include "bitstream.h"
28 : :
29 : : #include "internaltypes.h"
30 : :
31 : : #include "brass_check.h"
32 : : #include "brass_cursor.h"
33 : : #include "brass_table.h"
34 : : #include "brass_types.h"
35 : : #include "pack.h"
36 : : #include "valuestats.h"
37 : :
38 : : #include <xapian.h>
39 : :
40 : : #include "autoptr.h"
41 : : #include <iostream>
42 : :
43 : : using namespace std;
44 : :
45 : : static inline bool
46 : 0 : is_user_metadata_key(const string & key)
47 : : {
48 [ # # ][ # # ]: 0 : return key.size() > 1 && key[0] == '\0' && key[1] == '\xc0';
[ # # ]
49 : : }
50 : :
51 : 0 : struct VStats : public ValueStats {
52 : : Xapian::doccount freq_real;
53 : :
54 : 0 : VStats() : ValueStats(), freq_real(0) {}
55 : : };
56 : :
57 : : size_t
58 : 3 : check_brass_table(const char * tablename, string filename, int opts,
59 : : vector<Xapian::termcount> & doclens,
60 : : Xapian::docid db_last_docid)
61 : : {
62 : 3 : filename += '.';
63 : :
64 : : // Check the btree structure.
65 : 3 : BrassTableCheck::check(tablename, filename, opts);
66 : :
67 : : // Now check the brass structures inside the btree.
68 : 3 : BrassTable table(tablename, filename, true);
69 : 3 : table.open();
70 : 3 : AutoPtr<BrassCursor> cursor(table.cursor_get());
71 : :
72 : 3 : size_t errors = 0;
73 : :
74 : 3 : cursor->find_entry("");
75 : 3 : cursor->next(); // Skip the empty entry.
76 : :
77 [ + + ]: 3 : if (strcmp(tablename, "postlist") == 0) {
78 : : // Now check the structure of each postlist in the table.
79 : 1 : map<Xapian::valueno, VStats> valuestats;
80 : 1 : string current_term;
81 : 1 : Xapian::docid lastdid = 0;
82 : 1 : Xapian::termcount termfreq = 0, collfreq = 0;
83 : 1 : Xapian::termcount tf = 0, cf = 0;
84 : 1 : bool have_metainfo_key = false;
85 : :
86 : : // The first key/tag pair should be the METAINFO - though this may be
87 : : // missing if the table only contains user-metadata.
88 [ + - ]: 1 : if (!cursor->after_end()) {
89 [ + - ]: 1 : if (cursor->current_key == string("", 1)) {
90 : 1 : have_metainfo_key = true;
91 : 1 : cursor->read_tag();
92 : : // Check format of the METAINFO key.
93 : : totlen_t total_doclen;
94 : : Xapian::docid last_docid;
95 : : Xapian::termcount doclen_lbound;
96 : : Xapian::termcount doclen_ubound;
97 : : Xapian::termcount wdf_ubound;
98 : :
99 : 1 : const char * data = cursor->current_tag.data();
100 : 1 : const char * end = data + cursor->current_tag.size();
101 [ - + ]: 1 : if (!unpack_uint(&data, end, &last_docid)) {
102 : 0 : cout << "Tag containing meta information is corrupt (couldn't read last_docid)." << endl;
103 : 0 : ++errors;
104 [ - + ]: 1 : } else if (!unpack_uint(&data, end, &doclen_lbound)) {
105 : 0 : cout << "Tag containing meta information is corrupt (couldn't read doclen_lbound)." << endl;
106 : 0 : ++errors;
107 [ - + ]: 1 : } else if (!unpack_uint(&data, end, &wdf_ubound)) {
108 : 0 : cout << "Tag containing meta information is corrupt (couldn't read wdf_ubound)." << endl;
109 : 0 : ++errors;
110 [ - + ]: 1 : } else if (!unpack_uint(&data, end, &doclen_ubound)) {
111 : 0 : cout << "Tag containing meta information is corrupt (couldn't read doclen_ubound)." << endl;
112 : 0 : ++errors;
113 [ - + ]: 1 : } else if (!unpack_uint_last(&data, end, &total_doclen)) {
114 : 0 : cout << "Tag containing meta information is corrupt (couldn't read total_doclen)." << endl;
115 : 0 : ++errors;
116 [ - + ]: 1 : } else if (data != end) {
117 : 0 : cout << "Tag containing meta information is corrupt (junk at end)." << endl;
118 : 0 : ++errors;
119 : : }
120 : 1 : cursor->next();
121 : : }
122 : : }
123 : :
124 [ - + ]: 1 : for ( ; !cursor->after_end(); cursor->next()) {
125 : 0 : string & key = cursor->current_key;
126 : :
127 [ # # ]: 0 : if (is_user_metadata_key(key)) {
128 : : // User metadata can be anything, so we can't do any particular
129 : : // checks on it other than to check that the tag isn't empty.
130 : 0 : cursor->read_tag();
131 [ # # ]: 0 : if (cursor->current_tag.empty()) {
132 : 0 : cout << "User metadata item is empty" << endl;
133 : 0 : ++errors;
134 : : }
135 : 0 : continue;
136 : : }
137 : :
138 [ # # ]: 0 : if (!have_metainfo_key) {
139 : 0 : cout << "METAINFO key missing from postlist table" << endl;
140 : 0 : ++errors;
141 : : }
142 : :
143 [ # # ][ # # ]: 0 : if (key.size() >= 2 && key[0] == '\0' && key[1] == '\xe0') {
[ # # ][ # # ]
144 : : // doclen chunk
145 : : const char * pos, * end;
146 : 0 : Xapian::docid did = 1;
147 [ # # ]: 0 : if (key.size() > 2) {
148 : : // Non-initial chunk.
149 : 0 : pos = key.data();
150 : 0 : end = pos + key.size();
151 : 0 : pos += 2;
152 [ # # ]: 0 : if (!unpack_uint_preserving_sort(&pos, end, &did)) {
153 : 0 : cout << "Error unpacking docid from doclen key" << endl;
154 : 0 : ++errors;
155 : 0 : continue;
156 : : }
157 : : }
158 : :
159 : 0 : cursor->read_tag();
160 : 0 : pos = cursor->current_tag.data();
161 : 0 : end = pos + cursor->current_tag.size();
162 [ # # ]: 0 : if (key.size() == 2) {
163 : : // Initial chunk.
164 [ # # ][ # # ]: 0 : if (end - pos < 2 || pos[0] || pos[1]) {
[ # # ]
165 : 0 : cout << "Initial doclen chunk has nonzero dummy fields" << endl;
166 : 0 : ++errors;
167 : 0 : continue;
168 : : }
169 : 0 : pos += 2;
170 [ # # ]: 0 : if (!unpack_uint(&pos, end, &did)) {
171 : 0 : cout << "Failed to unpack firstdid for doclen" << endl;
172 : 0 : ++errors;
173 : 0 : continue;
174 : : }
175 : 0 : ++did;
176 [ # # ]: 0 : if (did <= lastdid) {
177 : : cout << "First did in this chunk is <= last in "
178 : 0 : "prev chunk" << endl;
179 : 0 : ++errors;
180 : : }
181 : : }
182 : :
183 : : bool is_last_chunk;
184 [ # # ]: 0 : if (!unpack_bool(&pos, end, &is_last_chunk)) {
185 : 0 : cout << "Failed to unpack last chunk flag for doclen" << endl;
186 : 0 : ++errors;
187 : 0 : continue;
188 : : }
189 : : // Read what the final document ID in this chunk is.
190 [ # # ]: 0 : if (!unpack_uint(&pos, end, &lastdid)) {
191 : 0 : cout << "Failed to unpack increase to last" << endl;
192 : 0 : ++errors;
193 : 0 : continue;
194 : : }
195 : 0 : lastdid += did;
196 : 0 : bool bad = false;
197 : 0 : while (true) {
198 : : Xapian::termcount doclen;
199 [ # # ]: 0 : if (!unpack_uint(&pos, end, &doclen)) {
200 : 0 : cout << "Failed to unpack doclen" << endl;
201 : 0 : ++errors;
202 : 0 : bad = true;
203 : 0 : break;
204 : : }
205 : :
206 [ # # ]: 0 : if (did > db_last_docid) {
207 : : cout << "document id " << did << " in doclen stream "
208 : : << "is larger than get_last_docid() "
209 : 0 : << db_last_docid << endl;
210 : 0 : ++errors;
211 : : }
212 : :
213 [ # # ]: 0 : if (!doclens.empty()) {
214 : : // In brass, a document without terms doesn't get a
215 : : // termlist entry.
216 : 0 : Xapian::termcount termlist_doclen = 0;
217 [ # # ]: 0 : if (did < doclens.size())
218 : 0 : termlist_doclen = doclens[did];
219 : :
220 [ # # ]: 0 : if (doclen != termlist_doclen) {
221 : : cout << "document id " << did << ": length "
222 : : << doclen << " doesn't match "
223 : : << termlist_doclen << " in the termlist table"
224 : 0 : << endl;
225 : 0 : ++errors;
226 : : }
227 : : }
228 : :
229 [ # # ]: 0 : if (pos == end) break;
230 : :
231 : : Xapian::docid inc;
232 [ # # ]: 0 : if (!unpack_uint(&pos, end, &inc)) {
233 : 0 : cout << "Failed to unpack docid increase" << endl;
234 : 0 : ++errors;
235 : 0 : bad = true;
236 : 0 : break;
237 : : }
238 : 0 : ++inc;
239 : 0 : did += inc;
240 [ # # ]: 0 : if (did > lastdid) {
241 : : cout << "docid " << did << " > last docid " << lastdid
242 : 0 : << endl;
243 : 0 : ++errors;
244 : : }
245 : : }
246 [ # # ]: 0 : if (bad) {
247 : 0 : continue;
248 : : }
249 [ # # ]: 0 : if (is_last_chunk) {
250 [ # # ]: 0 : if (did != lastdid) {
251 : : cout << "lastdid " << lastdid << " != last did " << did
252 : 0 : << endl;
253 : 0 : ++errors;
254 : : }
255 : : }
256 : :
257 : 0 : continue;
258 : : }
259 : :
260 [ # # ][ # # ]: 0 : if (key.size() >= 2 && key[0] == '\0' && key[1] == '\xd0') {
[ # # ][ # # ]
261 : : // Value stats.
262 : 0 : const char * p = key.data();
263 : 0 : const char * end = p + key.length();
264 : 0 : p += 2;
265 : : Xapian::valueno slot;
266 [ # # ]: 0 : if (!unpack_uint_last(&p, end, &slot)) {
267 : 0 : cout << "Bad valuestats key (no slot)" << endl;
268 : 0 : ++errors;
269 : 0 : continue;
270 : : }
271 : :
272 : 0 : cursor->read_tag();
273 : 0 : p = cursor->current_tag.data();
274 : 0 : end = p + cursor->current_tag.size();
275 : :
276 : 0 : VStats & v = valuestats[slot];
277 [ # # ]: 0 : if (!unpack_uint(&p, end, &v.freq)) {
278 [ # # ]: 0 : if (*p == 0) {
279 : 0 : cout << "Incomplete stats item in value table" << endl;
280 : : } else {
281 : 0 : cout << "Frequency statistic in value table is too large" << endl;
282 : : }
283 : 0 : ++errors;
284 : 0 : continue;
285 : : }
286 [ # # ]: 0 : if (!unpack_string(&p, end, v.lower_bound)) {
287 [ # # ]: 0 : if (*p == 0) {
288 : 0 : cout << "Incomplete stats item in value table" << endl;
289 : : } else {
290 : 0 : cout << "Lower bound statistic in value table is too large" << endl;
291 : : }
292 : 0 : ++errors;
293 : 0 : continue;
294 : : }
295 : 0 : size_t len = end - p;
296 [ # # ]: 0 : if (len == 0) {
297 : 0 : v.upper_bound = v.lower_bound;
298 : : } else {
299 : 0 : v.upper_bound.assign(p, len);
300 : : }
301 : :
302 : 0 : continue;
303 : : }
304 : :
305 [ # # ][ # # ]: 0 : if (key.size() >= 2 && key[0] == '\0' && key[1] == '\xd8') {
[ # # ][ # # ]
306 : : // Value stream chunk.
307 : 0 : const char * p = key.data();
308 : 0 : const char * end = p + key.length();
309 : 0 : p += 2;
310 : : Xapian::valueno slot;
311 [ # # ]: 0 : if (!unpack_uint(&p, end, &slot)) {
312 : 0 : cout << "Bad value chunk key (no slot)" << endl;
313 : 0 : ++errors;
314 : 0 : continue;
315 : : }
316 : : Xapian::docid did;
317 [ # # ]: 0 : if (!unpack_uint_preserving_sort(&p, end, &did)) {
318 : 0 : cout << "Bad value chunk key (no docid)" << endl;
319 : 0 : ++errors;
320 : 0 : continue;
321 : : }
322 [ # # ]: 0 : if (p != end) {
323 : 0 : cout << "Bad value chunk key (trailing junk)" << endl;
324 : 0 : ++errors;
325 : 0 : continue;
326 : : }
327 : :
328 : 0 : VStats & v = valuestats[slot];
329 : :
330 : 0 : cursor->read_tag();
331 : 0 : p = cursor->current_tag.data();
332 : 0 : end = p + cursor->current_tag.size();
333 : :
334 [ # # ]: 0 : while (true) {
335 : 0 : string value;
336 [ # # ]: 0 : if (!unpack_string(&p, end, value)) {
337 : 0 : cout << "Failed to unpack value from chunk" << endl;
338 : 0 : ++errors;
339 : : break;
340 : : }
341 : :
342 : 0 : ++v.freq_real;
343 : :
344 : : // FIXME: Cross-check that docid did has value slot (and
345 : : // vice versa - that there's a value here if the slot entry
346 : : // says so).
347 : :
348 : : // FIXME: Check if the bounds are tight? Or is that better
349 : : // as a separate tool which can also update the bounds?
350 [ # # ]: 0 : if (value < v.lower_bound) {
351 : : cout << "Value slot " << slot << " has value below "
352 : : "lower bound: '" << value << "' < '"
353 : 0 : << v.lower_bound << "'" << endl;
354 : 0 : ++errors;
355 [ # # ]: 0 : } else if (value > v.upper_bound) {
356 : : cout << "Value slot " << slot << " has value above "
357 : : "upper bound: '" << value << "' > '"
358 : 0 : << v.upper_bound << "'" << endl;
359 : 0 : ++errors;
360 : : }
361 : :
362 [ # # ]: 0 : if (p == end) break;
363 : : Xapian::docid delta;
364 [ # # ]: 0 : if (!unpack_uint(&p, end, &delta)) {
365 : 0 : cout << "Failed to unpack docid delta from chunk" << endl;
366 : 0 : ++errors;
367 : : break;
368 : : }
369 : 0 : Xapian::docid new_did = did + delta + 1;
370 [ # # ]: 0 : if (new_did <= did) {
371 : 0 : cout << "docid overflowed in value chunk" << endl;
372 : 0 : ++errors;
373 : : break;
374 : : }
375 : 0 : did = new_did;
376 : :
377 [ # # ]: 0 : if (did > db_last_docid) {
378 : : cout << "document id " << did << " in value chunk "
379 : : << "is larger than get_last_docid() "
380 : 0 : << db_last_docid << endl;
381 : 0 : ++errors;
382 : : }
383 : : }
384 : 0 : continue;
385 : : }
386 : :
387 : : const char * pos, * end;
388 : :
389 : : // Get term from key.
390 : 0 : pos = key.data();
391 : 0 : end = pos + key.size();
392 : :
393 : 0 : string term;
394 : : Xapian::docid did;
395 [ # # ]: 0 : if (!unpack_string_preserving_sort(&pos, end, term)) {
396 : 0 : cout << "Error unpacking termname from key" << endl;
397 : 0 : ++errors;
398 : 0 : continue;
399 : : }
400 [ # # ][ # # ]: 0 : if (!current_term.empty() && term != current_term) {
[ # # ]
401 : : // The term changed unexpectedly.
402 [ # # ]: 0 : if (pos == end) {
403 : : cout << "No last chunk for term `" << current_term
404 : 0 : << "'" << endl;
405 : 0 : current_term.resize(0);
406 : : } else {
407 : : cout << "Mismatch in follow-on chunk in posting "
408 : : "list for term `" << current_term << "' (got `"
409 : 0 : << term << "')" << endl;
410 : 0 : current_term = term;
411 : 0 : tf = cf = 0;
412 : 0 : lastdid = 0;
413 : : }
414 : 0 : ++errors;
415 : : }
416 [ # # ]: 0 : if (pos == end) {
417 : : // First chunk.
418 [ # # ]: 0 : if (term == current_term) {
419 : : // This probably isn't possible.
420 : : cout << "First posting list chunk for term `"
421 : : << term << "' follows previous chunk for the same "
422 : 0 : "term" << endl;
423 : 0 : ++errors;
424 : : }
425 : 0 : current_term = term;
426 : 0 : tf = cf = 0;
427 : :
428 : : // Unpack extra header from first chunk.
429 : 0 : cursor->read_tag();
430 : 0 : pos = cursor->current_tag.data();
431 : 0 : end = pos + cursor->current_tag.size();
432 [ # # ]: 0 : if (!unpack_uint(&pos, end, &termfreq)) {
433 : : cout << "Failed to unpack termfreq for term `" << term
434 : 0 : << "'" << endl;
435 : 0 : ++errors;
436 : 0 : continue;
437 : : }
438 [ # # ]: 0 : if (!unpack_uint(&pos, end, &collfreq)) {
439 : : cout << "Failed to unpack collfreq for term `" << term
440 : 0 : << "'" << endl;
441 : 0 : ++errors;
442 : 0 : continue;
443 : : }
444 [ # # ]: 0 : if (!unpack_uint(&pos, end, &did)) {
445 : : cout << "Failed to unpack firstdid for term `" << term
446 : 0 : << "'" << endl;
447 : 0 : ++errors;
448 : 0 : continue;
449 : : }
450 : 0 : ++did;
451 : : } else {
452 : : // Continuation chunk.
453 [ # # ]: 0 : if (current_term.empty()) {
454 : : cout << "First chunk for term `" << current_term << "' "
455 : 0 : "is a continuation chunk" << endl;
456 : 0 : ++errors;
457 : 0 : current_term = term;
458 : : }
459 : : AssertEq(current_term, term);
460 [ # # ]: 0 : if (!unpack_uint_preserving_sort(&pos, end, &did)) {
461 : 0 : cout << "Failed to unpack did from key" << endl;
462 : 0 : ++errors;
463 : 0 : continue;
464 : : }
465 [ # # ]: 0 : if (did <= lastdid) {
466 : : cout << "First did in this chunk is <= last in "
467 : 0 : "prev chunk" << endl;
468 : 0 : ++errors;
469 : : }
470 : 0 : cursor->read_tag();
471 : 0 : pos = cursor->current_tag.data();
472 : 0 : end = pos + cursor->current_tag.size();
473 : : }
474 : :
475 : : bool is_last_chunk;
476 [ # # ]: 0 : if (!unpack_bool(&pos, end, &is_last_chunk)) {
477 : 0 : cout << "Failed to unpack last chunk flag" << endl;
478 : 0 : ++errors;
479 : 0 : continue;
480 : : }
481 : : // Read what the final document ID in this chunk is.
482 [ # # ]: 0 : if (!unpack_uint(&pos, end, &lastdid)) {
483 : 0 : cout << "Failed to unpack increase to last" << endl;
484 : 0 : ++errors;
485 : 0 : continue;
486 : : }
487 : 0 : lastdid += did;
488 : 0 : bool bad = false;
489 : 0 : while (true) {
490 : : Xapian::termcount wdf;
491 [ # # ]: 0 : if (!unpack_uint(&pos, end, &wdf)) {
492 : 0 : cout << "Failed to unpack wdf" << endl;
493 : 0 : ++errors;
494 : 0 : bad = true;
495 : 0 : break;
496 : : }
497 : 0 : ++tf;
498 : 0 : cf += wdf;
499 : :
500 [ # # ]: 0 : if (pos == end) break;
501 : :
502 : : Xapian::docid inc;
503 [ # # ]: 0 : if (!unpack_uint(&pos, end, &inc)) {
504 : 0 : cout << "Failed to unpack docid increase" << endl;
505 : 0 : ++errors;
506 : 0 : bad = true;
507 : 0 : break;
508 : : }
509 : 0 : ++inc;
510 : 0 : did += inc;
511 [ # # ]: 0 : if (did > lastdid) {
512 : : cout << "docid " << did << " > last docid " << lastdid
513 : 0 : << endl;
514 : 0 : ++errors;
515 : : }
516 : : }
517 [ # # ]: 0 : if (bad) {
518 : 0 : continue;
519 : : }
520 [ # # ]: 0 : if (is_last_chunk) {
521 [ # # ]: 0 : if (tf != termfreq) {
522 : : cout << "termfreq " << termfreq << " != # of entries "
523 : 0 : << tf << endl;
524 : 0 : ++errors;
525 : : }
526 [ # # ]: 0 : if (cf != collfreq) {
527 : : cout << "collfreq " << collfreq << " != sum wdf " << cf
528 : 0 : << endl;
529 : 0 : ++errors;
530 : : }
531 [ # # ]: 0 : if (did != lastdid) {
532 : : cout << "lastdid " << lastdid << " != last did " << did
533 : 0 : << endl;
534 : 0 : ++errors;
535 : : }
536 : 0 : current_term.resize(0);
537 : : }
538 : : }
539 [ - + ]: 1 : if (!current_term.empty()) {
540 : : cout << "Last term `" << current_term << "' has no last chunk"
541 : 0 : << endl;
542 : 0 : ++errors;
543 : : }
544 : :
545 : 1 : map<Xapian::valueno, VStats>::const_iterator i;
546 [ - + ]: 1 : for (i = valuestats.begin(); i != valuestats.end(); ++i) {
547 [ # # ]: 0 : if (i->second.freq != i->second.freq_real) {
548 : : cout << "Value stats frequency for slot " << i->first << " is "
549 : : << i->second.freq << " but recounting gives "
550 : 0 : << i->second.freq_real << endl;
551 : 0 : ++errors;
552 : : }
553 : 1 : }
554 [ + + ]: 2 : } else if (strcmp(tablename, "record") == 0) {
555 : : // Now check the contents of the record table. Any data is valid as
556 : : // the tag so we don't check the tags.
557 [ - + ]: 1 : for ( ; !cursor->after_end(); cursor->next()) {
558 : 0 : string & key = cursor->current_key;
559 : :
560 : : // Get docid from key.
561 : 0 : const char * pos = key.data();
562 : 0 : const char * end = pos + key.size();
563 : :
564 : : Xapian::docid did;
565 [ # # ]: 0 : if (!unpack_uint_preserving_sort(&pos, end, &did)) {
566 : 0 : cout << "Error unpacking docid from key" << endl;
567 : 0 : ++errors;
568 [ # # ]: 0 : } else if (pos != end) {
569 : 0 : cout << "Extra junk in key" << endl;
570 : 0 : ++errors;
571 : : }
572 : : }
573 [ + - ]: 1 : } else if (strcmp(tablename, "termlist") == 0) {
574 : : // Now check the contents of the termlist table.
575 [ - + ]: 1 : for ( ; !cursor->after_end(); cursor->next()) {
576 : 0 : string & key = cursor->current_key;
577 : :
578 : : // Get docid from key.
579 : 0 : const char * pos = key.data();
580 : 0 : const char * end = pos + key.size();
581 : :
582 : : Xapian::docid did;
583 [ # # ]: 0 : if (!unpack_uint_preserving_sort(&pos, end, &did)) {
584 : 0 : cout << "Error unpacking docid from key" << endl;
585 : 0 : ++errors;
586 : 0 : continue;
587 : : }
588 : :
589 [ # # ][ # # ]: 0 : if (end - pos == 1 && *pos == '\0') {
590 : : // Value slots used entry.
591 : 0 : cursor->read_tag();
592 : :
593 : 0 : pos = cursor->current_tag.data();
594 : 0 : end = pos + cursor->current_tag.size();
595 : :
596 [ # # ]: 0 : if (pos == end) {
597 : 0 : cout << "Empty value slots used tag" << endl;
598 : 0 : ++errors;
599 : 0 : continue;
600 : : }
601 : :
602 : : Xapian::valueno prev_slot;
603 [ # # ]: 0 : if (!unpack_uint(&pos, end, &prev_slot)) {
604 : 0 : cout << "Value slot encoding corrupt" << endl;
605 : 0 : ++errors;
606 : 0 : continue;
607 : : }
608 : :
609 [ # # ]: 0 : while (pos != end) {
610 : : Xapian::valueno slot;
611 [ # # ]: 0 : if (!unpack_uint(&pos, end, &slot)) {
612 : 0 : cout << "Value slot encoding corrupt" << endl;
613 : 0 : ++errors;
614 : 0 : break;
615 : : }
616 : 0 : slot += prev_slot + 1;
617 [ # # ]: 0 : if (slot <= prev_slot) {
618 : 0 : cout << "Value slot number overflowed (" << prev_slot << " -> " << slot << ")" << endl;
619 : 0 : ++errors;
620 : : }
621 : 0 : prev_slot = slot;
622 : : }
623 : 0 : continue;
624 : : }
625 : :
626 [ # # ]: 0 : if (pos != end) {
627 : 0 : cout << "Extra junk in key" << endl;
628 : 0 : ++errors;
629 : 0 : continue;
630 : : }
631 : :
632 : 0 : cursor->read_tag();
633 : :
634 : 0 : pos = cursor->current_tag.data();
635 : 0 : end = pos + cursor->current_tag.size();
636 : :
637 [ # # ]: 0 : if (pos == end) {
638 : : // Empty termlist.
639 : 0 : continue;
640 : : }
641 : :
642 : : Xapian::termcount doclen, termlist_size;
643 : :
644 : : // Read doclen
645 [ # # ]: 0 : if (!unpack_uint(&pos, end, &doclen)) {
646 [ # # ]: 0 : if (pos != 0) {
647 : 0 : cout << "doclen out of range" << endl;
648 : : } else {
649 : 0 : cout << "Unexpected end of data when reading doclen" << endl;
650 : : }
651 : 0 : ++errors;
652 : 0 : continue;
653 : : }
654 : :
655 : : // Read termlist_size
656 [ # # ]: 0 : if (!unpack_uint(&pos, end, &termlist_size)) {
657 [ # # ]: 0 : if (pos != 0) {
658 : 0 : cout << "termlist_size out of range" << endl;
659 : : } else {
660 : 0 : cout << "Unexpected end of data when reading termlist_size" << endl;
661 : : }
662 : 0 : ++errors;
663 : 0 : continue;
664 : : }
665 : :
666 : 0 : Xapian::termcount actual_doclen = 0, actual_termlist_size = 0;
667 : 0 : string current_tname;
668 : :
669 : 0 : bool bad = false;
670 [ # # ]: 0 : while (pos != end) {
671 : 0 : Xapian::doccount current_wdf = 0;
672 : 0 : bool got_wdf = false;
673 : : // If there was a previous term, how much to reuse.
674 [ # # ]: 0 : if (!current_tname.empty()) {
675 : 0 : string::size_type len = static_cast<unsigned char>(*pos++);
676 [ # # ]: 0 : if (len > current_tname.length()) {
677 : : // The wdf was squeezed into the same byte.
678 : 0 : current_wdf = len / (current_tname.length() + 1) - 1;
679 : 0 : len %= (current_tname.length() + 1);
680 : 0 : got_wdf = true;
681 : : }
682 : 0 : current_tname.resize(len);
683 : : }
684 : : // What to append (note len must be positive, since just truncating
685 : : // always takes us backwards in the sort order)
686 : 0 : string::size_type len = static_cast<unsigned char>(*pos++);
687 : 0 : current_tname.append(pos, len);
688 : 0 : pos += len;
689 : :
690 [ # # ]: 0 : if (!got_wdf) {
691 : : // Read wdf
692 [ # # ]: 0 : if (!unpack_uint(&pos, end, ¤t_wdf)) {
693 [ # # ]: 0 : if (pos == 0) {
694 : 0 : cout << "Unexpected end of data when reading termlist current_wdf" << endl;
695 : : } else {
696 : 0 : cout << "Size of wdf out of range, in termlist" << endl;
697 : : }
698 : 0 : ++errors;
699 : 0 : bad = true;
700 : 0 : break;
701 : : }
702 : : }
703 : :
704 : 0 : ++actual_termlist_size;
705 : 0 : actual_doclen += current_wdf;
706 : : }
707 [ # # ]: 0 : if (bad) {
708 : 0 : continue;
709 : : }
710 : :
711 [ # # ]: 0 : if (termlist_size != actual_termlist_size) {
712 : 0 : cout << "termlist_size != # of entries in termlist" << endl;
713 : 0 : ++errors;
714 : : }
715 [ # # ]: 0 : if (doclen != actual_doclen) {
716 : 0 : cout << "doclen != sum(wdf)" << endl;
717 : 0 : ++errors;
718 : : }
719 : :
720 : : // + 1 so that did is a valid subscript.
721 [ # # ]: 0 : if (doclens.size() <= did) doclens.resize(did + 1);
722 : 0 : doclens[did] = actual_doclen;
723 : : }
724 [ # # ]: 0 : } else if (strcmp(tablename, "position") == 0) {
725 : : // Now check the contents of the position table.
726 [ # # ]: 0 : for ( ; !cursor->after_end(); cursor->next()) {
727 : 0 : string & key = cursor->current_key;
728 : :
729 : : // Get docid from key.
730 : 0 : const char * pos = key.data();
731 : 0 : const char * end = pos + key.size();
732 : :
733 : : Xapian::docid did;
734 [ # # ]: 0 : if (!unpack_uint_preserving_sort(&pos, end, &did)) {
735 : 0 : cout << "Error unpacking docid from key" << endl;
736 : 0 : ++errors;
737 : 0 : continue;
738 : : }
739 [ # # ]: 0 : if (pos == end) {
740 : 0 : cout << "No termname in key" << endl;
741 : 0 : ++errors;
742 : 0 : continue;
743 : : }
744 : :
745 : 0 : cursor->read_tag();
746 : :
747 : 0 : const string & data = cursor->current_tag;
748 : 0 : pos = data.data();
749 : 0 : end = pos + data.size();
750 : :
751 : : Xapian::termpos pos_last;
752 [ # # ]: 0 : if (!unpack_uint(&pos, end, &pos_last)) {
753 : 0 : cout << tablename << " table: Position list data corrupt" << endl;
754 : 0 : ++errors;
755 : 0 : continue;
756 : : }
757 [ # # ]: 0 : if (pos == end) {
758 : : // Special case for single entry position list.
759 : : } else {
760 : : // Skip the header we just read.
761 : 0 : BitReader rd(data, pos - data.data());
762 : 0 : Xapian::termpos pos_first = rd.decode(pos_last);
763 : 0 : Xapian::termpos pos_size = rd.decode(pos_last - pos_first) + 2;
764 : 0 : vector<Xapian::termpos> positions;
765 : 0 : positions.resize(pos_size);
766 : 0 : positions[0] = pos_first;
767 : 0 : positions.back() = pos_last;
768 : 0 : rd.decode_interpolative(positions, 0, pos_size - 1);
769 : 0 : vector<Xapian::termpos>::const_iterator current_pos = positions.begin();
770 : 0 : Xapian::termpos lastpos = *current_pos++;
771 [ # # ]: 0 : while (current_pos != positions.end()) {
772 : 0 : Xapian::termpos termpos = *current_pos++;
773 [ # # ]: 0 : if (termpos <= lastpos) {
774 : 0 : cout << tablename << " table: Positions not strictly monotonically increasing" << endl;
775 : 0 : ++errors;
776 : 0 : break;
777 : : }
778 : 0 : lastpos = termpos;
779 : 0 : }
780 : : }
781 : : }
782 : : } else {
783 : 0 : cout << tablename << " table: Don't know how to check structure\n" << endl;
784 : 0 : return errors;
785 : : }
786 : :
787 [ + - ]: 3 : if (!errors)
788 : 3 : cout << tablename << " table structure checked OK\n" << endl;
789 : : else
790 : 0 : cout << tablename << " table errors found: " << errors << "\n" << endl;
791 : :
792 : 3 : return errors;
793 [ + - ][ + - ]: 15 : }
|