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