1 /* MDB Tools - A library for reading MS Access database file
2 * Copyright (C) 2000 Brian Bruns
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 */
18
19 #include <stdio.h>
20
21 #ifdef HAVE_LIBREADLINE
22 # if defined(HAVE_READLINE_READLINE_H)
23 # include <readline/readline.h>
24 # elif defined(HAVE_READLINE_H)
25 # include <readline.h>
26 # else
27 /* no readline.h */
28 extern char *readline ();
29 # endif
30 char *cmdline = NULL;
31 #endif /* HAVE_LIBREADLINE */
32
33 #ifdef HAVE_READLINE_HISTORY
34 # if defined(HAVE_READLINE_HISTORY_H)
35 # include <readline/history.h>
36 # elif defined(HAVE_HISTORY_H)
37 # include <history.h>
38 # else
39 /* no history.h */
40 extern void add_history ();
41 extern int write_history ();
42 extern int read_history ();
43 extern void clear_history ();
44 # endif
45 #endif /* HAVE_READLINE_HISTORY */
46
47 #include <string.h>
48 #include "mdbsql.h"
49 #include "mdbver.h"
50
51 void dump_results(FILE *out, MdbSQL *sql, char *delimiter);
52 void dump_results_pp(FILE *out, MdbSQL *sql);
53
54 int headers = 1;
55 int footers = 1;
56 int pretty_print = 1;
57 int showplan = 0;
58 int noexec = 0;
59
60 #ifdef HAVE_READLINE_HISTORY
61 #define HISTFILE ".mdbhistory"
62 #endif
63
64 #ifndef HAVE_LIBREADLINE
readline(char * prompt)65 char *readline(char *prompt)
66 {
67 char line[1000];
68 int i = 0;
69
70 fputs(prompt, stdout);
71 if (!fgets(line, sizeof(line), stdin)) {
72 return NULL;
73 }
74 for (i=strlen(line);i>0;i--) {
75 if (line[i]=='\n') {
76 line[i]='\0';
77 break;
78 }
79 }
80
81 return g_strdup(line);
82 }
83 #endif
84
strlen_utf(const char * s)85 static int strlen_utf(const char *s) {
86 int len = 0;
87 while (*s) {
88 if ((*s++ & 0xc0) != 0x80)
89 len++;
90 }
91 return len;
92 }
93
94 void
do_set_cmd(MdbSQL * sql,char * s)95 do_set_cmd(MdbSQL *sql, char *s)
96 {
97 char *level1, *level2;
98 level1 = strtok(s, " \t\n");
99 if (!level1) {
100 printf("Usage: set [stats|showplan|noexec] [on|off]\n");
101 return;
102 }
103 if (!strcmp(level1,"stats")) {
104 level2 = strtok(NULL, " \t");
105 if (!level2) {
106 printf("Usage: set stats [on|off]\n");
107 return;
108 }
109 if (!strcmp(level2,"on")) {
110 mdb_stats_on(sql->mdb);
111 } else if (!strcmp(level2,"off")) {
112 mdb_stats_off(sql->mdb);
113 mdb_dump_stats(sql->mdb);
114 } else {
115 printf("Unknown stats option %s\n", level2);
116 printf("Usage: set stats [on|off]\n");
117 }
118 } else if (!strcmp(level1,"showplan")) {
119 level2 = strtok(NULL, " \t");
120 if (!level2) {
121 printf("Usage: set showplan [on|off]\n");
122 return;
123 }
124 if (!strcmp(level2,"on")) {
125 showplan=1;
126 } else if (!strcmp(level2,"off")) {
127 showplan=0;
128 } else {
129 printf("Unknown showplan option %s\n", level2);
130 printf("Usage: set showplan [on|off]\n");
131 }
132 } else if (!strcmp(level1,"noexec")) {
133 level2 = strtok(NULL, " \t");
134 if (!level2) {
135 printf("Usage: set noexec [on|off]\n");
136 return;
137 }
138 if (!strcmp(level2,"on")) {
139 noexec=1;
140 } else if (!strcmp(level2,"off")) {
141 noexec=0;
142 } else {
143 printf("Unknown noexec option %s\n", level2);
144 printf("Usage: set noexec [on|off]\n");
145 }
146 } else {
147 printf("Unknown set command %s\n", level1);
148 printf("Usage: set [stats|showplan|noexec] [on|off]\n");
149 }
150 }
151
152 void
run_query(FILE * out,MdbSQL * sql,char * mybuf,char * delimiter)153 run_query(FILE *out, MdbSQL *sql, char *mybuf, char *delimiter)
154 {
155 MdbTableDef *table;
156
157 mdb_sql_run_query(sql, mybuf);
158 if (!mdb_sql_has_error(sql)) {
159 if (showplan) {
160 table = sql->cur_table;
161 if (table->sarg_tree) mdb_sql_dump_node(table->sarg_tree, 0);
162 if (sql->cur_table->strategy == MDB_TABLE_SCAN)
163 printf("Table scanning %s\n", table->name);
164 else
165 printf("Index scanning %s using %s\n", table->name, table->scan_idx->name);
166 }
167 /* If noexec != on, dump results */
168 if (!noexec) {
169 if (pretty_print)
170 dump_results_pp(out, sql);
171 else
172 dump_results(out, sql, delimiter);
173 }
174 mdb_sql_reset(sql);
175 }
176 }
177
print_value(FILE * out,char * v,int sz,int first)178 void print_value(FILE *out, char *v, int sz, int first)
179 {
180 int i;
181 int vlen;
182
183 if (first)
184 fputc('|', out);
185 vlen = strlen_utf(v);
186 fputs(v, out);
187 for (i=vlen;i<sz;i++)
188 fputc(' ', out);
189 fputc('|', out);
190 }
print_break(FILE * out,int sz,int first)191 static void print_break(FILE *out, int sz, int first)
192 {
193 int i;
194 if (first)
195 fputc('+', out);
196 for (i=0;i<sz;i++)
197 fputc('-', out);
198 fputc('+', out);
199 }
print_rows_retrieved(FILE * out,unsigned long row_count)200 void print_rows_retrieved(FILE *out, unsigned long row_count)
201 {
202 if (!row_count)
203 fprintf(out, "No Rows retrieved\n");
204 else if (row_count==1)
205 fprintf(out, "1 Row retrieved\n");
206 else
207 fprintf(out, "%lu Rows retrieved\n", row_count);
208 fflush(out);
209 }
210 void
dump_results(FILE * out,MdbSQL * sql,char * delimiter)211 dump_results(FILE *out, MdbSQL *sql, char *delimiter)
212 {
213 unsigned int j;
214 MdbSQLColumn *sqlcol;
215
216 if (headers) {
217 for (j=0;j<sql->num_columns-1;j++) {
218 sqlcol = g_ptr_array_index(sql->columns,j);
219 fprintf(out, "%s%s", sqlcol->name,
220 delimiter ? delimiter : "\t");
221 }
222 sqlcol = g_ptr_array_index(sql->columns,sql->num_columns-1);
223 fprintf(out, "%s", sqlcol->name);
224 fprintf(out,"\n");
225 fflush(out);
226 }
227 while(mdb_sql_fetch_row(sql, sql->cur_table)) {
228 for (j=0;j<sql->num_columns-1;j++) {
229 sqlcol = g_ptr_array_index(sql->columns,j);
230 fprintf(out, "%s%s", (char*)(g_ptr_array_index(sql->bound_values, j)),
231 delimiter ? delimiter : "\t");
232 }
233 sqlcol = g_ptr_array_index(sql->columns,sql->num_columns-1);
234 fprintf(out, "%s", (char*)(g_ptr_array_index(sql->bound_values, sql->num_columns-1)));
235 fprintf(out,"\n");
236 fflush(out);
237 }
238 if (footers) {
239 print_rows_retrieved(out, sql->row_count);
240 }
241 }
242
243 void
dump_results_pp(FILE * out,MdbSQL * sql)244 dump_results_pp(FILE *out, MdbSQL *sql)
245 {
246 unsigned int j;
247 MdbSQLColumn *sqlcol;
248
249 /* print header */
250 if (headers) {
251 for (j=0;j<sql->num_columns;j++) {
252 sqlcol = g_ptr_array_index(sql->columns,j);
253 if (strlen(sqlcol->name)>(size_t)sqlcol->disp_size)
254 sqlcol->disp_size = strlen(sqlcol->name);
255 print_break(out, sqlcol->disp_size, !j);
256 }
257 fprintf(out,"\n");
258 fflush(out);
259 for (j=0;j<sql->num_columns;j++) {
260 sqlcol = g_ptr_array_index(sql->columns,j);
261 print_value(out, sqlcol->name,sqlcol->disp_size,!j);
262 }
263 fprintf(out,"\n");
264 fflush(out);
265 }
266
267 for (j=0;j<sql->num_columns;j++) {
268 sqlcol = g_ptr_array_index(sql->columns,j);
269 print_break(out, sqlcol->disp_size, !j);
270 }
271 fprintf(out,"\n");
272 fflush(out);
273
274 /* print each row */
275 while(mdb_sql_fetch_row(sql, sql->cur_table)) {
276 for (j=0;j<sql->num_columns;j++) {
277 sqlcol = g_ptr_array_index(sql->columns,j);
278 print_value(out, (char *) g_ptr_array_index(sql->bound_values, j), sqlcol->disp_size,!j);
279 }
280 fprintf(out,"\n");
281 fflush(out);
282 }
283
284 /* footer */
285 for (j=0;j<sql->num_columns;j++) {
286 sqlcol = g_ptr_array_index(sql->columns,j);
287 print_break(out, sqlcol->disp_size, !j);
288 }
289 fprintf(out,"\n");
290 fflush(out);
291 if (footers) {
292 print_rows_retrieved(out, sql->row_count);
293 }
294 }
295
296 static char *
find_sql_terminator(char * s)297 find_sql_terminator(char *s)
298 {
299 char *sp;
300 int len = strlen(s);
301
302 if (len == 0) {
303 return NULL;
304 }
305
306 sp = &s[len-1];
307 while (sp > s && isspace(*sp)) {
308 sp--;
309 }
310
311 if (*sp == ';') {
312 return sp;
313 }
314
315 return NULL;
316 }
317
318 int
main(int argc,char ** argv)319 main(int argc, char **argv)
320 {
321 char *s = NULL;
322 char prompt[20];
323 int line = 0;
324 char *mybuf;
325 unsigned int bufsz;
326 MdbSQL *sql;
327 FILE *in = NULL, *out = NULL;
328 char *filename_in=NULL, *filename_out=NULL;
329 #ifdef HAVE_READLINE_HISTORY
330 char *home = getenv("HOME");
331 char *histpath;
332 #endif
333 char *delimiter = NULL;
334 int in_from_colon_r = 0;
335 char *locale = NULL;
336 int print_mdbver = 0;
337
338 GOptionEntry entries[] = {
339 { "delim", 'd', 0, G_OPTION_ARG_STRING, &delimiter, "Use this delimiter.", "char"},
340 { "no-pretty-print", 'P', G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &pretty_print, "Don't pretty print", NULL},
341 { "no-header", 'H', G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &headers, "Don't print header", NULL},
342 { "no-footer", 'F', G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &footers, "Don't print footer", NULL},
343 { "input", 'i', 0, G_OPTION_ARG_FILENAME, &filename_in, "Read SQL from specified file", "file"},
344 { "output", 'o', 0, G_OPTION_ARG_FILENAME, &filename_out, "Write result to specified file", "file"},
345 {"version", 0, 0, G_OPTION_ARG_NONE, &print_mdbver, "Show mdbtools version and exit", NULL},
346 { NULL },
347 };
348 GError *error = NULL;
349 GOptionContext *opt_context;
350
351 opt_context = g_option_context_new("<file> - Run SQL");
352 g_option_context_add_main_entries(opt_context, entries, NULL /*i18n*/);
353 // g_option_context_set_strict_posix(opt_context, TRUE); /* options first, requires glib 2.44 */
354 locale = setlocale(LC_CTYPE, "");
355 if (!g_option_context_parse (opt_context, &argc, &argv, &error))
356 {
357 fprintf(stderr, "option parsing failed: %s\n", error->message);
358 fputs(g_option_context_get_help(opt_context, TRUE, NULL), stderr);
359 exit (1);
360 }
361 if (print_mdbver) {
362 if (argc > 1) {
363 fputs(g_option_context_get_help(opt_context, TRUE, NULL), stderr);
364 }
365 fprintf(stdout,"%s\n", MDB_FULL_VERSION);
366 exit(argc > 1);
367 }
368 setlocale(LC_CTYPE, locale);
369
370 if (argc > 2) {
371 fputs("Wrong number of arguments.\n\n", stderr);
372 fputs(g_option_context_get_help(opt_context, TRUE, NULL), stderr);
373 exit(1);
374 }
375
376 #ifdef HAVE_READLINE_HISTORY
377 if (home) {
378 histpath = (char *) g_strconcat(home, "/", HISTFILE, NULL);
379 read_history(histpath);
380 g_free(histpath);
381 }
382 #endif
383 /* If input is coming from a pipe */
384 if (!isatty(fileno(stdin))) {
385 in = stdin;
386 }
387 if (filename_in) {
388 if (!strcmp(filename_in, "stdin"))
389 in = stdin;
390 else if (!(in = fopen(filename_in, "r"))) {
391 fprintf(stderr, "Unable to open file %s\n", filename_in);
392 exit(1);
393 }
394 }
395 if (filename_out) {
396 if (!(out = fopen(filename_out, "w"))) {
397 fprintf(stderr,"Unable to open file %s\n", filename_out);
398 exit(1);
399 }
400 }
401
402 setlocale(LC_COLLATE, "");
403 /* initialize the SQL engine */
404 sql = mdb_sql_init();
405 if (argc == 2) {
406 mdb_sql_open(sql, argv[1]);
407 }
408
409 /* give the buffer an initial size */
410 bufsz = 4096;
411 mybuf = g_malloc(bufsz);
412 mybuf[0]='\0';
413
414 while (1) {
415 line ++;
416 if (s) {
417 free(s);
418 s = NULL;
419 }
420
421 if (in) {
422 s=calloc(bufsz, 1);
423 if (!fgets(s, bufsz, in)) {
424 // Backwards compatibility with older MDBTools
425 // Files read from the command line had an
426 // implicit "go" at the end
427 if (!in_from_colon_r && strlen(mybuf))
428 strcpy(s, "go");
429 else if (in_from_colon_r) {
430 line = 0;
431 fclose(in);
432 in = NULL;
433 in_from_colon_r = 0;
434 } else
435 break;
436 } else if (s[strlen(s)-1]=='\n')
437 s[strlen(s)-1]=0;
438 } else {
439 snprintf(prompt, sizeof(prompt), "%d => ", line);
440 locale = setlocale(LC_CTYPE, "");
441 char *l = readline(prompt);
442 setlocale(LC_CTYPE, locale);
443 if (!l)
444 break;
445 s=g_locale_to_utf8(l, -1, NULL, NULL, NULL);
446 free(l);
447 }
448
449 if (!strcmp(s,"exit") || !strcmp(s,"quit") || !strcmp(s,"bye"))
450 break;
451
452 if (line==1 && (!strncmp(s,"set ",4) || !strcmp(s,"set"))) {
453 do_set_cmd(sql, &s[3]);
454 line = 0;
455 } else if (!strcmp(s,"go")) {
456 line = 0;
457 run_query((out) ? out : stdout, sql, mybuf, delimiter);
458 mybuf[0]='\0';
459 } else if (!strcmp(s,"reset")) {
460 line = 0;
461 mybuf[0]='\0';
462 } else if (!strncmp(s,":r",2)) {
463 char *fname = &s[2];
464 if (in) {
465 fprintf(stderr, "Can not handle nested opens\n");
466 } else {
467 while (*fname && isspace(*fname))
468 fname++;
469 if (!(in = fopen(fname, "r"))) {
470 fprintf(stderr,"Unable to open file %s\n", fname);
471 mybuf[0]=0;
472 } else {
473 in_from_colon_r = 1;
474 }
475 }
476 } else {
477 char *p;
478
479 while (strlen(mybuf) + strlen(s) > bufsz) {
480 bufsz *= 2;
481 mybuf = (char *) g_realloc(mybuf, bufsz);
482 }
483 #ifdef HAVE_READLINE_HISTORY
484 /* don't record blank lines, or lines read from files
485 * specified on the command line */
486 if ((!in || in_from_colon_r) && strlen(s))
487 add_history(s);
488 #endif
489 strcat(mybuf,s);
490 /* preserve line numbering for the parser */
491 strcat(mybuf,"\n");
492
493 if ((p = find_sql_terminator(mybuf))) {
494 *p = '\0';
495 line = 0;
496 run_query((out) ? out : stdout, sql, mybuf, delimiter);
497 mybuf[0]='\0';
498 }
499 }
500 }
501 mdb_sql_exit(sql);
502
503 g_free(mybuf);
504 if (s) free(s);
505 if (out) fclose(out);
506 if ((in) && (in != stdin)) fclose(in);
507
508 #ifdef HAVE_READLINE_HISTORY
509 if (home) {
510 histpath = (char *) g_strconcat(home, "/", HISTFILE, NULL);
511 write_history(histpath);
512 g_free(histpath);
513 clear_history();
514 }
515 #endif
516
517 g_option_context_free(opt_context);
518 g_free(delimiter);
519 g_free(filename_in);
520 g_free(filename_out);
521
522 return 0;
523 }
524