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