1 /** @file xapian-inspect.cc
2 * @brief Inspect the contents of a glass table for development or debugging.
4 /* Copyright (C) 2007,2008,2009,2010,2011,2012,2017,2018 Olly Betts
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
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.
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 USA
26 #include <cstdio> // For sprintf().
28 #include "glass_cursor.h"
29 #include "glass_table.h"
30 #include "glass_version.h"
31 #include "filetests.h"
32 #include "stringutils.h"
36 #include "gnu_getopt.h"
40 #define PROG_NAME "xapian-inspect"
41 #define PROG_DESC "Inspect the contents of a glass table for development or debugging"
46 static bool keys
= true, tags
= true;
48 static void show_usage() {
49 cout
<< "Usage: " PROG_NAME
" [OPTIONS] TABLE\n"
50 " " PROG_NAME
" [OPTIONS] -t TABLE DB\n\n"
52 " -t, --table=TABLE which table to inspect\n"
53 " --help display this help and exit\n"
54 " --version output version information and exit" << endl
;
58 display_nicely(const string
& data
) {
59 string::const_iterator i
;
60 for (i
= data
.begin(); i
!= data
.end(); ++i
) {
61 unsigned char ch
= *i
;
62 if (ch
< 32 || ch
>= 127) {
64 case '\n': cout
<< "\\n"; break;
65 case '\r': cout
<< "\\r"; break;
66 case '\t': cout
<< "\\t"; break;
69 sprintf(buf
, "\\x%02x", int(ch
));
73 } else if (ch
== '\\') {
81 // Reverse display_nicely() encoding.
83 unescape(const string
& s
)
85 auto bslash
= s
.find('\\');
86 if (bslash
== string::npos
)
88 string
r(s
, 0, bslash
);
89 for (auto i
= s
.begin() + bslash
; i
!= s
.end(); ++i
) {
99 // \0 is not output by display_nicely(), but would
100 // reasonably be expected to work.
119 if (!C_isxdigit(ch1
) || !C_isxdigit(ch2
))
121 ch
= hex_digit(ch1
) << 4 | hex_digit(ch2
);
133 cout
<< "Bad escaping in specified key value, assuming literal"
141 cout
<< "Commands:\n"
142 "next : Next entry (alias 'n' or '')\n"
143 "prev : Previous entry (alias 'p')\n"
144 "first : First entry (alias 'f')\n"
145 "last : Last entry (alias 'l')\n"
146 "goto K : Goto first entry with key >= K (alias 'g')\n"
147 "until K: Display entries until key >= K (alias 'u')\n"
148 "until : Display entries until end (alias 'u')\n"
149 "open T : Open table T instead (alias 'o') - e.g. open postlist\n"
150 "keys : Toggle showing keys (default: true) (alias 'k')\n"
151 "tags : Toggle showing tags (default: true) (alias 't')\n"
152 "help : Show this (alias 'h' or '?')\n"
153 "quit : Quit this utility (alias 'q')" << endl
;
157 show_entry(GlassCursor
& cursor
)
159 if (cursor
.after_end()) {
160 cout
<< "After end" << endl
;
163 if (cursor
.current_key
.empty()) {
164 cout
<< "Before start" << endl
;
169 display_nicely(cursor
.current_key
);
175 display_nicely(cursor
.current_tag
);
181 do_until(GlassCursor
& cursor
, const string
& target
)
183 if (cursor
.after_end()) {
184 cout
<< "At end already." << endl
;
188 if (!target
.empty()) {
189 int cmp
= target
.compare(cursor
.current_key
);
192 cout
<< "Already after specified key." << endl
;
194 cout
<< "Already at specified key." << endl
;
200 while (cursor
.next()) {
207 int cmp
= target
.compare(cursor
.current_key
);
209 cout
<< "No exact match, stopping at entry after, "
210 "having advanced by " << count
<< " entries." << endl
;
214 cout
<< "Advanced by " << count
<< " entries." << endl
;
219 cout
<< "Reached end, having advanced by " << count
<< " entries." << endl
;
223 goto_last(GlassCursor
& cursor
)
225 // To position on the last key we just do a < search for a key greater than
226 // any possible key - one longer than the longest possible length and
227 // consisting entirely of the highest sorting byte value.
228 cursor
.find_entry_lt(string(GLASS_BTREE_MAX_KEY_LEN
+ 1, '\xff'));
232 main(int argc
, char **argv
)
234 static const struct option long_opts
[] = {
235 {"table", required_argument
, 0, 't'},
236 {"help", no_argument
, 0, OPT_HELP
},
237 {"version", no_argument
, 0, OPT_VERSION
},
244 while ((c
= gnu_getopt_long(argc
, argv
, "t:", long_opts
, 0)) != -1) {
250 cout
<< PROG_NAME
" - " PROG_DESC
"\n\n";
254 cout
<< PROG_NAME
" - " PACKAGE_STRING
<< endl
;
262 if (argc
- optind
!= 1) {
267 // Path to the DB to inspect (possibly with a table name appended).
268 string
db_path(argv
[optind
]);
269 bool arg_is_directory
= dir_exists(db_path
);
270 if (arg_is_directory
&& table_name
.empty()) {
272 << ": You need to specify a table name to inspect with --table."
276 int single_file_fd
= -1;
277 if (table_name
.empty()) {
278 // db_path should be a path to a table, possibly without the extension
279 // or with just a trailing '.' (supported mostly for historical
280 // reasons). First normalise away any extension or trailing '.'.
281 if (endswith(db_path
, "." GLASS_TABLE_EXTENSION
)) {
282 db_path
.resize(db_path
.size() -
283 CONST_STRLEN(GLASS_TABLE_EXTENSION
) - 1);
284 } else if (endswith(db_path
, '.')) {
285 db_path
.resize(db_path
.size() - 1);
287 size_t slash
= db_path
.find_last_of(DIR_SEPS
);
288 // If slash is std::string::npos, this assigns the whole of db_path to
289 // table_name, which is what we want.
290 table_name
.assign(db_path
, slash
+ 1, string::npos
);
291 if (slash
!= string::npos
) {
292 db_path
.resize(slash
);
296 } else if (!arg_is_directory
) {
297 single_file_fd
= open(db_path
.c_str(), O_RDONLY
| O_BINARY
);
298 if (single_file_fd
< 0) {
299 cerr
<< argv
[0] << ": Couldn't open file '" << db_path
<< "'"
305 GlassVersion
* version_file_ptr
;
306 if (single_file_fd
< 0) {
307 version_file_ptr
= new GlassVersion(db_path
);
309 version_file_ptr
= new GlassVersion(single_file_fd
);
311 GlassVersion
& version_file
= *version_file_ptr
;
314 glass_revision_number_t rev
= version_file
.get_revision();
319 open_different_table
:
321 Glass::table_type table_code
;
322 if (table_name
== "docdata") {
323 table_code
= Glass::DOCDATA
;
324 } else if (table_name
== "spelling") {
325 table_code
= Glass::SPELLING
;
326 } else if (table_name
== "synonym") {
327 table_code
= Glass::SYNONYM
;
328 } else if (table_name
== "termlist") {
329 table_code
= Glass::TERMLIST
;
330 } else if (table_name
== "position") {
331 table_code
= Glass::POSITION
;
332 } else if (table_name
== "postlist") {
333 table_code
= Glass::POSTLIST
;
335 cerr
<< "Unknown table: '" << table_name
<< "'" << endl
;
339 GlassTable
* table_ptr
;
340 if (single_file_fd
< 0) {
341 string table_path
= db_path
;
343 table_path
+= table_name
;
345 table_ptr
= new GlassTable("", table_path
, true);
347 auto offset
= version_file
.get_offset();
348 table_ptr
= new GlassTable("", single_file_fd
, offset
, true);
350 GlassTable
& table
= *table_ptr
;
352 table
.open(0, version_file
.get_root(table_code
), rev
);
354 cout
<< "No entries!" << endl
;
357 cout
<< "Table has " << table
.get_entry_count() << " entries" << endl
;
359 GlassCursor
cursor(&table
);
366 cout
<< "? " << flush
;
370 if (cin
.eof()) break;
372 if (endswith(input
, '\r'))
373 input
.resize(input
.size() - 1);
375 if (input
.empty() || input
== "n" || input
== "next") {
376 if (cursor
.after_end()) {
377 cout
<< "At end already." << endl
;
382 } else if (input
== "p" || input
== "prev") {
383 if (cursor
.current_key
.empty()) {
384 cout
<< "Before start already." << endl
;
387 // If the cursor has fallen off the end, point it back at the
389 if (cursor
.after_end()) {
393 cursor
.find_entry_lt(cursor
.current_key
);
395 } else if (startswith(input
, "u ")) {
396 do_until(cursor
, unescape(input
.substr(2)));
398 } else if (startswith(input
, "until ")) {
399 do_until(cursor
, unescape(input
.substr(6)));
401 } else if (input
== "u" || input
== "until") {
402 do_until(cursor
, string());
404 } else if (input
== "f" || input
== "first") {
408 } else if (input
== "l" || input
== "last") {
411 } else if (startswith(input
, "g ")) {
412 if (!cursor
.find_entry_ge(unescape(input
.substr(2)))) {
413 cout
<< "No exact match, going to entry after." << endl
;
416 } else if (startswith(input
, "goto ")) {
417 if (!cursor
.find_entry_ge(unescape(input
.substr(5)))) {
418 cout
<< "No exact match, going to entry after." << endl
;
421 } else if (startswith(input
, "o ") || startswith(input
, "open ")) {
422 size_t trim
= (input
[1] == ' ' ? 2 : 5);
423 table_name
.assign(input
, trim
, string::npos
);
424 if (endswith(table_name
, "." GLASS_TABLE_EXTENSION
))
425 table_name
.resize(table_name
.size() -
426 CONST_STRLEN(GLASS_TABLE_EXTENSION
) - 1);
427 else if (endswith(table_name
, '.'))
428 table_name
.resize(table_name
.size() - 1);
429 goto open_different_table
;
430 } else if (input
== "t" || input
== "tags") {
432 cout
<< "Showing tags: " << boolalpha
<< tags
<< endl
;
433 } else if (input
== "k" || input
== "keys") {
435 cout
<< "Showing keys: " << boolalpha
<< keys
<< endl
;
436 } else if (input
== "q" || input
== "quit") {
438 } else if (input
== "h" || input
== "help" || input
== "?") {
442 cout
<< "Unknown command." << endl
;
446 } catch (const Xapian::Error
&error
) {
447 cerr
<< argv
[0] << ": " << error
.get_description() << endl
;