xapian-core  1.4.26
xapian-inspect.cc
Go to the documentation of this file.
1 
4 /* Copyright (C) 2007,2008,2009,2010,2011,2012,2017,2018,2023 Olly Betts
5  *
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.
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 USA
19  */
20 
21 #include <config.h>
22 
23 #include <ios>
24 #include <iostream>
25 #include <string>
26 
27 #include "glass_cursor.h"
28 #include "glass_table.h"
29 #include "glass_version.h"
30 #include "filetests.h"
31 #include "stringutils.h"
32 
33 #include <xapian.h>
34 
35 #include "gnu_getopt.h"
36 
37 using namespace std;
38 
39 #define PROG_NAME "xapian-inspect"
40 #define PROG_DESC "Inspect the contents of a glass table for development or debugging"
41 
42 #define OPT_HELP 1
43 #define OPT_VERSION 2
44 
45 static bool keys = true, tags = true;
46 
47 static void show_usage() {
48  cout << "Usage: " PROG_NAME " [OPTIONS] TABLE\n"
49  " " PROG_NAME " [OPTIONS] -t TABLE DB\n\n"
50 "Options:\n"
51 " -t, --table=TABLE which table to inspect\n"
52 " --help display this help and exit\n"
53 " --version output version information and exit\n";
54 }
55 
56 static void
57 display_nicely(const string& data)
58 {
59  for (unsigned char ch : data) {
60  if (ch < 32 || ch >= 127) {
61  switch (ch) {
62  case '\n': cout << "\\n"; break;
63  case '\r': cout << "\\r"; break;
64  case '\t': cout << "\\t"; break;
65  default: {
66  cout << "\\x"
67  << "0123456789abcdef"[ch >> 4]
68  << "0123456789abcdef"[ch & 0x0f];
69  }
70  }
71  } else if (ch == '\\') {
72  cout << "\\\\";
73  } else {
74  cout << ch;
75  }
76  }
77 }
78 
79 // Reverse display_nicely() encoding.
80 static string
81 unescape(const string& s)
82 {
83  auto bslash = s.find('\\');
84  if (bslash == string::npos)
85  return s;
86  string r(s, 0, bslash);
87  for (auto i = s.begin() + bslash; i != s.end(); ++i) {
88  char ch = *i;
89  if (ch == '\\') {
90  if (++i == s.end())
91  goto bad_escaping;
92  ch = *i;
93  switch (ch) {
94  case '\\':
95  break;
96  case '0':
97  // \0 is not output by display_nicely(), but would
98  // reasonably be expected to work.
99  ch = '\0';
100  break;
101  case 'n':
102  ch = '\n';
103  break;
104  case 'r':
105  ch = '\r';
106  break;
107  case 't':
108  ch = '\t';
109  break;
110  case 'x': {
111  if (++i == s.end())
112  goto bad_escaping;
113  char ch1 = *i;
114  if (++i == s.end())
115  goto bad_escaping;
116  char ch2 = *i;
117  if (!C_isxdigit(ch1) || !C_isxdigit(ch2))
118  goto bad_escaping;
119  ch = hex_decode(ch1, ch2);
120  break;
121  }
122  default:
123  goto bad_escaping;
124  }
125  }
126  r += ch;
127  }
128  return r;
129 
130 bad_escaping:
131  cout << "Bad escaping in specified key value, assuming literal\n";
132  return s;
133 }
134 
135 static void
137 {
138  cout << "Commands:\n"
139  "next : Next entry (alias 'n' or '')\n"
140  "prev : Previous entry (alias 'p')\n"
141  "first : First entry (alias 'f')\n"
142  "last : Last entry (alias 'l')\n"
143  "goto K : Goto first entry with key >= K (alias 'g')\n"
144  "until K: Display entries until key >= K (alias 'u')\n"
145  "until : Display entries until end (alias 'u')\n"
146  "count K: Count entries until key >= K (alias 'c')\n"
147  "count : Count entries until end (alias 'c')\n"
148  "open T : Open table T instead (alias 'o') - e.g. open postlist\n"
149  "keys : Toggle showing keys (default: true) (alias 'k')\n"
150  "tags : Toggle showing tags (default: true) (alias 't')\n"
151  "help : Show this (alias 'h' or '?')\n"
152  "quit : Quit this utility (alias 'q')\n";
153 }
154 
155 static void
157 {
158  if (cursor.after_end()) {
159  cout << "After end\n";
160  return;
161  }
162  if (cursor.current_key.empty()) {
163  cout << "Before start\n";
164  return;
165  }
166  if (keys) {
167  cout << "Key: ";
168  display_nicely(cursor.current_key);
169  cout << '\n';
170  }
171  if (tags) {
172  cout << "Tag: ";
173  cursor.read_tag();
174  display_nicely(cursor.current_tag);
175  cout << '\n';
176  }
177 }
178 
179 static void
180 do_until(GlassCursor& cursor, const string& target, bool show)
181 {
182  if (cursor.after_end()) {
183  cout << "At end already.\n";
184  return;
185  }
186 
187  if (!target.empty()) {
188  int cmp = target.compare(cursor.current_key);
189  if (cmp <= 0) {
190  if (cmp)
191  cout << "Already after specified key.\n";
192  else
193  cout << "Already at specified key.\n";
194  return;
195  }
196  }
197 
198  size_t count = 0;
199  while (cursor.next()) {
200  ++count;
201  if (show) show_entry(cursor);
202 
203  if (target.empty())
204  continue;
205 
206  int cmp = target.compare(cursor.current_key);
207  if (cmp < 0) {
208  cout << "No exact match, stopping at entry after, "
209  "having advanced by " << count << " entries.\n";
210  return;
211  }
212  if (cmp == 0) {
213  cout << "Advanced by " << count << " entries.\n";
214  return;
215  }
216  }
217 
218  cout << "Reached end, having advanced by " << count << " entries.\n";
219 }
220 
221 static void
223 {
224  // To position on the last key we just do a < search for a key greater than
225  // any possible key - one longer than the longest possible length and
226  // consisting entirely of the highest sorting byte value.
227  cursor.find_entry_lt(string(GLASS_BTREE_MAX_KEY_LEN + 1, '\xff'));
228 }
229 
230 int
231 main(int argc, char** argv)
232 {
233  static const struct option long_opts[] = {
234  {"table", required_argument, 0, 't'},
235  {"help", no_argument, 0, OPT_HELP},
236  {"version", no_argument, 0, OPT_VERSION},
237  {NULL, 0, 0, 0}
238  };
239 
240  string table_name;
241 
242  int c;
243  while ((c = gnu_getopt_long(argc, argv, "t:", long_opts, 0)) != -1) {
244  switch (c) {
245  case 't':
246  table_name = optarg;
247  break;
248  case OPT_HELP:
249  cout << PROG_NAME " - " PROG_DESC "\n\n";
250  show_usage();
251  exit(0);
252  case OPT_VERSION:
253  cout << PROG_NAME " - " PACKAGE_STRING "\n";
254  exit(0);
255  default:
256  show_usage();
257  exit(1);
258  }
259  }
260 
261  if (argc - optind != 1) {
262  show_usage();
263  exit(1);
264  }
265 
266  // Path to the DB to inspect (possibly with a table name appended).
267  string db_path(argv[optind]);
268  bool arg_is_directory = dir_exists(db_path);
269  if (arg_is_directory && table_name.empty()) {
270  cerr << argv[0]
271  << ": You need to specify a table name to inspect with "
272  "--table.\n";
273  exit(1);
274  }
275  int single_file_fd = -1;
276  if (table_name.empty()) {
277  // db_path should be a path to a table, possibly without the extension
278  // or with just a trailing '.' (supported mostly for historical
279  // reasons). First normalise away any extension or trailing '.'.
280  if (endswith(db_path, "." GLASS_TABLE_EXTENSION)) {
281  db_path.resize(db_path.size() -
283  } else if (endswith(db_path, '.')) {
284  db_path.resize(db_path.size() - 1);
285  }
286  size_t slash = db_path.find_last_of(DIR_SEPS);
287  // If slash is std::string::npos, this assigns the whole of db_path to
288  // table_name, which is what we want.
289  table_name.assign(db_path, slash + 1, string::npos);
290  if (slash != string::npos) {
291  db_path.resize(slash);
292  } else {
293  db_path.resize(0);
294  }
295  } else if (!arg_is_directory) {
296  single_file_fd = open(db_path.c_str(), O_RDONLY | O_BINARY);
297  if (single_file_fd < 0) {
298  cerr << argv[0] << ": Couldn't open file '" << db_path << "'"
299  << endl;
300  exit(1);
301  }
302  }
303 
304  GlassVersion* version_file_ptr;
305  if (single_file_fd < 0) {
306  version_file_ptr = new GlassVersion(db_path);
307  } else {
308  version_file_ptr = new GlassVersion(single_file_fd);
309  }
310  GlassVersion& version_file = *version_file_ptr;
311 
312  version_file.read();
313  glass_revision_number_t rev = version_file.get_revision();
314 
315  show_help();
316  cout << '\n';
317 
318 open_different_table:
319  try {
320  Glass::table_type table_code;
321  if (table_name == "docdata") {
322  table_code = Glass::DOCDATA;
323  } else if (table_name == "spelling") {
324  table_code = Glass::SPELLING;
325  } else if (table_name == "synonym") {
326  table_code = Glass::SYNONYM;
327  } else if (table_name == "termlist") {
328  table_code = Glass::TERMLIST;
329  } else if (table_name == "position") {
330  table_code = Glass::POSITION;
331  } else if (table_name == "postlist") {
332  table_code = Glass::POSTLIST;
333  } else {
334  cerr << "Unknown table: '" << table_name << "'\n";
335  exit(1);
336  }
337 
338  GlassTable* table_ptr;
339  if (single_file_fd < 0) {
340  string table_path = db_path;
341  table_path += '/';
342  table_path += table_name;
343  table_path += '.';
344  table_ptr = new GlassTable("", table_path, true);
345  } else {
346  auto offset = version_file.get_offset();
347  table_ptr = new GlassTable("", single_file_fd, offset, true);
348  }
349  GlassTable& table = *table_ptr;
350 
351  table.open(0, version_file.get_root(table_code), rev);
352  if (table.empty()) {
353  cout << "No entries!\n";
354  exit(0);
355  }
356  cout << "Table has " << table.get_entry_count() << " entries\n";
357 
358  GlassCursor cursor(&table);
359  cursor.find_entry_ge(string());
360  cursor.next();
361 
362  while (!cin.eof()) {
363  show_entry(cursor);
364 wait_for_input:
365  cout << "? " << flush;
366 
367  string input;
368  getline(cin, input);
369  if (cin.eof()) break;
370 
371  if (endswith(input, '\r'))
372  input.resize(input.size() - 1);
373 
374  if (input.empty() || input == "n" || input == "next") {
375  if (cursor.after_end()) {
376  cout << "At end already.\n";
377  goto wait_for_input;
378  }
379  (void)cursor.next();
380  continue;
381  } else if (input == "p" || input == "prev") {
382  if (cursor.current_key.empty()) {
383  cout << "Before start already.\n";
384  goto wait_for_input;
385  }
386  // If the cursor has fallen off the end, point it back at the
387  // last entry.
388  if (cursor.after_end()) {
389  goto_last(cursor);
390  continue;
391  }
392  cursor.find_entry_lt(cursor.current_key);
393  continue;
394  } else if (startswith(input, "u ")) {
395  do_until(cursor, unescape(input.substr(2)), true);
396  goto wait_for_input;
397  } else if (startswith(input, "until ")) {
398  do_until(cursor, unescape(input.substr(6)), true);
399  goto wait_for_input;
400  } else if (input == "u" || input == "until") {
401  do_until(cursor, string(), true);
402  goto wait_for_input;
403  } else if (startswith(input, "c ")) {
404  do_until(cursor, unescape(input.substr(2)), false);
405  goto wait_for_input;
406  } else if (startswith(input, "count ")) {
407  do_until(cursor, unescape(input.substr(6)), false);
408  goto wait_for_input;
409  } else if (input == "c" || input == "count") {
410  do_until(cursor, string(), false);
411  goto wait_for_input;
412  } else if (input == "f" || input == "first") {
413  cursor.find_entry_ge(string());
414  cursor.next();
415  continue;
416  } else if (input == "l" || input == "last") {
417  goto_last(cursor);
418  continue;
419  } else if (startswith(input, "g ")) {
420  if (!cursor.find_entry_ge(unescape(input.substr(2)))) {
421  cout << "No exact match, going to entry after.\n";
422  }
423  continue;
424  } else if (startswith(input, "goto ")) {
425  if (!cursor.find_entry_ge(unescape(input.substr(5)))) {
426  cout << "No exact match, going to entry after.\n";
427  }
428  continue;
429  } else if (startswith(input, "o ") || startswith(input, "open ")) {
430  size_t trim = (input[1] == ' ' ? 2 : 5);
431  table_name.assign(input, trim, string::npos);
432  if (endswith(table_name, "." GLASS_TABLE_EXTENSION))
433  table_name.resize(table_name.size() -
435  else if (endswith(table_name, '.'))
436  table_name.resize(table_name.size() - 1);
437  goto open_different_table;
438  } else if (input == "t" || input == "tags") {
439  tags = !tags;
440  cout << "Showing tags: " << boolalpha << tags << '\n';
441  } else if (input == "k" || input == "keys") {
442  keys = !keys;
443  cout << "Showing keys: " << boolalpha << keys << '\n';
444  } else if (input == "q" || input == "quit") {
445  break;
446  } else if (input == "h" || input == "help" || input == "?") {
447  show_help();
448  goto wait_for_input;
449  } else {
450  cout << "Unknown command.\n";
451  goto wait_for_input;
452  }
453  }
454  } catch (const Xapian::Error& error) {
455  cerr << argv[0] << ": " << error.get_description() << '\n';
456  exit(1);
457  }
458 }
const RootInfo & get_root(Glass::table_type tbl) const
static void do_until(GlassCursor &cursor, const string &target, bool show)
bool endswith(const std::string &s, char sfx)
Definition: stringutils.h:75
Wrappers to allow GNU getopt to be used cleanly from C++ code.
GlassVersion class.
static void show_usage()
int optind
Definition: getopt.cc:94
XAPIAN_REVISION_TYPE rev
Revision number of a database.
Definition: types.h:133
table_type
Definition: glass_defs.h:53
int gnu_getopt_long(int argc_, char *const *argv_, const char *shortopts_, const struct option *longopts_, int *optind_)
Definition: gnu_getopt.h:97
#define GLASS_BTREE_MAX_KEY_LEN
The largest possible value of a key_len.
Definition: glass_table.h:57
bool empty() const
Return true if there are no entries in the table.
Definition: glass_table.h:681
#define OPT_HELP
Class managing a Btree table in a Glass database.
Definition: glass_table.h:425
void find_entry_lt(const string &key)
Position the cursor on the highest entry with key < key.
uint4 glass_revision_number_t
The revision number of a glass database.
Definition: glass_defs.h:68
The GlassVersion class manages the revision files.
Definition: glass_version.h:94
Definition: header.h:63
#define DIR_SEPS
Definition: config.h:8
#define O_BINARY
Definition: safefcntl.h:81
WritableDatabase open()
Construct a WritableDatabase object for a new, empty InMemory database.
Definition: dbfactory.h:104
bool next()
Advance to the next key.
static bool tags
STL namespace.
off_t get_offset() const
bool after_end() const
Determine whether cursor is off the end of table.
Definition: glass_cursor.h:329
Utility functions for testing files.
#define GLASS_TABLE_EXTENSION
Glass table extension.
Definition: glass_defs.h:27
bool read_tag(bool keep_compressed=false)
Read the tag from the table and store it in current_tag.
#define no_argument
Definition: gnu_getopt.h:79
string current_key
Current key pointed to by cursor.
Definition: glass_cursor.h:239
static bool keys
Public interfaces for the Xapian library.
string current_tag
Current tag pointed to by cursor.
Definition: glass_cursor.h:244
char * optarg
Definition: getopt.cc:79
static void show_help()
#define CONST_STRLEN(S)
Returns the length of a string constant.
Definition: stringutils.h:43
bool startswith(const std::string &s, char pfx)
Definition: stringutils.h:51
#define required_argument
Definition: gnu_getopt.h:80
Btree implementation.
void read()
Read the version file and check it&#39;s a version we understand.
#define PROG_DESC
bool dir_exists(const char *path)
Test if a directory exists.
Definition: filetests.h:136
bool find_entry_ge(const string &key)
Position the cursor on the lowest entry with key >= key.
A cursor pointing to a position in a Btree table, for reading several entries in order, or finding approximate matches.
Definition: glass_cursor.h:147
std::string get_description() const
Return a string describing this object.
Definition: error.cc:93
void open(int flags_, const RootInfo &root_info, glass_revision_number_t rev)
Open the btree.
glass_tablesize_t get_entry_count() const
Return a count of the number of entries in the table.
Definition: glass_table.h:676
#define PROG_NAME
Interface to Btree cursors.
static string unescape(const string &s)
static void goto_last(GlassCursor &cursor)
All exceptions thrown by Xapian are subclasses of Xapian::Error.
Definition: error.h:43
char hex_decode(char ch1, char ch2)
Decode a pair of ASCII hex digits.
Definition: stringutils.h:243
Various handy helpers which std::string really should provide.
static void display_nicely(const string &data)
int main(int argc, char **argv)
#define PACKAGE_STRING
Definition: config.h:337
#define OPT_VERSION
static void show_entry(GlassCursor &cursor)
bool C_isxdigit(char ch)
Definition: stringutils.h:182
glass_revision_number_t get_revision() const