1/*
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 *
6 * Copyright 1997 - July 2008 CWI, August 2008 - 2019 MonetDB B.V.
7 */
8
9/* The Mapi Client Interface
10 * A textual interface to the Monet server using the Mapi library,
11 * providing command-line access for its users. It is the preferred
12 * interface for non-DBAs.
13 * See mclient.1 for usage information.
14 */
15
16#include "monetdb_config.h"
17#ifndef HAVE_GETOPT_LONG
18# include "monet_getopt.h"
19#else
20# ifdef HAVE_GETOPT_H
21# include "getopt.h"
22# endif
23#endif
24#include "mapi.h"
25#include <unistd.h>
26#include <string.h>
27#ifdef HAVE_STRINGS_H
28#include <strings.h> /* strcasecmp */
29#endif
30#include <sys/stat.h>
31
32#ifdef HAVE_LIBREADLINE
33#include <readline/readline.h>
34#include <readline/history.h>
35#include "ReadlineTools.h"
36#endif
37#include "stream.h"
38#include "msqldump.h"
39#define LIBMUTILS 1
40#include "mprompt.h"
41#include "mutils.h" /* mercurial_revision */
42#include "dotmonetdb.h"
43
44#include <locale.h>
45
46#ifdef HAVE_ICONV
47#ifdef HAVE_ICONV_H
48#include <iconv.h>
49#endif
50#ifdef HAVE_NL_LANGINFO
51#ifdef HAVE_LANGINFO_H
52#include <langinfo.h>
53#endif
54#endif
55#endif
56
57#ifndef S_ISCHR
58#define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR)
59#endif
60#ifndef S_ISREG
61#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
62#endif
63
64enum modes {
65 MAL,
66 SQL
67};
68
69static enum modes mode = SQL;
70static stream *toConsole;
71static stream *stdout_stream;
72static stream *stderr_stream;
73static stream *fromConsole = NULL;
74static char *language = NULL;
75static char *logfile = NULL;
76static char promptbuf[16];
77static bool echoquery = false;
78#ifdef HAVE_ICONV
79static char *encoding;
80#endif
81static bool errseen = false;
82static bool allow_remote = false;
83
84#define setPrompt() sprintf(promptbuf, "%.*s>", (int) sizeof(promptbuf) - 2, language)
85#define debugMode() (strncmp(promptbuf, "mdb", 3) == 0)
86
87/* the internal result set formatters */
88enum formatters {
89 NOformatter,
90 RAWformatter, // as the data is received
91 TABLEformatter, // render as a bordered table
92 CSVformatter, // render as a comma or tab separated values list
93 XMLformatter, // render as a valid XML document
94 TESTformatter, // for testing, escape characters
95 TRASHformatter, // remove the result set
96 ROWCOUNTformatter, // only print the number of rows returned
97 SAMformatter, // render a SAM result set
98 EXPANDEDformatter // render as multi-row single record
99};
100static enum formatters formatter = NOformatter;
101char *separator = NULL; /* column separator for CSV/TAB format */
102bool csvheader = false; /* include header line in CSV format */
103
104#define DEFWIDTH 80
105
106/* use a 64 bit integer for the timer */
107typedef int64_t timertype;
108
109static timertype t0, t1; /* used for timing */
110
111#define UTF8BOM "\xEF\xBB\xBF" /* UTF-8 encoding of Unicode BOM */
112#define UTF8BOMLENGTH 3 /* length of above */
113
114/* Pagination and simple ASCII-based rendering is provided for SQL
115 * sessions. The result set size is limited by the cache size of the
116 * Mapi Library. It is sufficiently large to accommodate most result
117 * to be browsed manually.
118 *
119 * The pagewidth determines the maximum space allocated for a single
120 * row. If the total space required is larger, then a heuristic
121 * routine is called to distribute the available space. Attribute
122 * values may then span multiple lines. Setting the pagewidth to 0
123 * turns off row size control. */
124
125#ifdef HAVE_POPEN
126static char *pager = 0; /* use external pager */
127#include <signal.h> /* to block SIGPIPE */
128#endif
129static int rowsperpage = 0; /* for SQL pagination */
130static int pagewidth = 0; /* -1: take whatever is necessary, >0: limit */
131static bool pagewidthset = false; /* whether the user set the width explicitly */
132static int croppedfields = 0; /* whatever got cropped/truncated */
133static bool firstcrop = true; /* first time we see cropping/truncation */
134
135enum modifiers {
136 NOmodifier,
137 DEBUGmodifier
138};
139static enum modifiers specials = NOmodifier;
140/* set when we see DEBUG (only if mode == SQL). Also retain these
141 * modes until after you have received the answer. */
142
143/* keep these aligned, the MINCOLSIZE should ensure you can always
144 * write the NULLSTRING */
145#define MINCOLSIZE 4
146static char default_nullstring[] = "null";
147static char *nullstring = default_nullstring;
148/* this is the minimum size (that still makes some sense) for writing
149 * variable length columns */
150#define MINVARCOLSIZE 10
151
152#include <time.h>
153#ifdef HAVE_FTIME
154#include <sys/timeb.h> /* ftime */
155#endif
156#ifdef HAVE_SYS_TIME_H
157#include <sys/time.h> /* gettimeofday */
158#endif
159#ifdef HAVE_STROPTS_H
160#include <stropts.h> /* ioctl on Solaris */
161#endif
162#ifdef HAVE_SYS_IOCTL_H
163#include <sys/ioctl.h>
164#endif
165#ifdef HAVE_TERMIOS_H
166#include <termios.h> /* TIOCGWINSZ/TIOCSWINSZ */
167#endif
168
169#if defined(_MSC_VER) && _MSC_VER >= 1400
170#define fileno _fileno
171#endif
172
173#define my_isspace(c) ((c) == '\f' || (c) == '\n' || (c) == ' ')
174
175#include <ctype.h>
176#include "mhelp.h"
177
178static timertype
179gettime(void)
180{
181 /* Return the time in milliseconds since an epoch. The epoch
182 is roughly the time this program started. */
183#ifdef _MSC_VER
184 static LARGE_INTEGER freq, start; /* automatically initialized to 0 */
185 LARGE_INTEGER ctr;
186
187 if (start.QuadPart == 0 &&
188 (!QueryPerformanceFrequency(&freq) ||
189 !QueryPerformanceCounter(&start)))
190 start.QuadPart = -1;
191 if (start.QuadPart > 0) {
192 QueryPerformanceCounter(&ctr);
193 return (timertype) (((ctr.QuadPart - start.QuadPart) * 1000000) / freq.QuadPart);
194 }
195#endif
196#ifdef HAVE_GETTIMEOFDAY
197 {
198 static struct timeval tpbase; /* automatically initialized to 0 */
199 struct timeval tp;
200
201 if (tpbase.tv_sec == 0)
202 gettimeofday(&tpbase, NULL);
203 gettimeofday(&tp, NULL);
204 tp.tv_sec -= tpbase.tv_sec;
205 return (timertype) tp.tv_sec * 1000000 + (timertype) tp.tv_usec;
206 }
207#else
208#ifdef HAVE_FTIME
209 {
210 static struct timeb tbbase; /* automatically initialized to 0 */
211 struct timeb tb;
212
213 if (tbbase.time == 0)
214 ftime(&tbbase);
215 ftime(&tb);
216 tb.time -= tbbase.time;
217 return (timertype) tb.time * 1000000 + (timertype) tb.millitm * 1000;
218 }
219#endif /* HAVE_FTIME */
220#endif /* HAVE_GETTIMEOFDAY */
221}
222
223static void
224timerStart(void)
225{
226 t0 = gettime();
227}
228
229static void
230timerPause(void)
231{
232 t1 = gettime();
233 if (t0 == 0)
234 t0 = t1;
235}
236
237static void
238timerResume(void)
239{
240 if (t1 == 0)
241 t1 = gettime();
242 assert(t1 >= t0);
243 t0 = gettime() - (t1 - t0);
244}
245
246static void
247timerEnd(void)
248{
249 mnstr_flush(toConsole);
250 t1 = gettime();
251 assert(t1 >= t0);
252}
253
254static timertype th = 0;
255static void
256timerHumanStop(void)
257{
258 th = gettime();
259}
260
261static enum itimers {
262 T_NONE = 0, // don't render the timing information
263 T_CLOCK, // render wallclock time in human readable format
264 T_PERF // return detailed performance
265} timermode = T_NONE;
266
267static bool timerHumanCalled = false;
268static void
269timerHuman(int64_t sqloptimizer, int64_t maloptimizer, int64_t querytime, bool singleinstr, bool total)
270{
271 timertype t = th - t0;
272
273 timerHumanCalled = true;
274
275 /*
276 * report only the times we do actually measure:
277 * - client-measured wall-clock time per query only when executing individual queries,
278 * otherwise only the total wall-clock time at the end of a batch;
279 * - server-measured detailed performance measures only per query.
280 */
281
282 /* "(singleinstr != total)" is C for (logical) "(singleinstr XOR total)" */
283 if (timermode == T_CLOCK && (singleinstr != total)) {
284 /* print wall-clock in "human-friendly" format */
285 fflush(stderr);
286 mnstr_flush(toConsole);
287 if (t / 1000 < 1000) {
288 fprintf(stderr, "clk: %" PRId64 ".%03d ms\n", t / 1000, (int) (t % 1000));
289 fflush(stderr);
290 return;
291 }
292 t /= 1000;
293 if (t / 1000 < 60) {
294 fprintf(stderr, "clk: %" PRId64 ".%03d sec\n", t / 1000, (int) (t % 1000));
295 fflush(stderr);
296 return;
297 }
298 t /= 1000;
299 if (t / 60 < 60) {
300 fprintf(stderr, "clk: %" PRId64 ":%02d min\n", t / 60, (int) (t % 60));
301 fflush(stderr);
302 return;
303 }
304 t /= 60;
305 fprintf(stderr, "clk: %" PRId64 ":%02d h\n", t / 60, (int) (t % 60));
306 fflush(stderr);
307 return;
308 }
309 if (timermode == T_PERF && (!total || singleinstr != total)) {
310 /* for performance measures we use milliseconds as the base */
311 fflush(stderr);
312 mnstr_flush(toConsole);
313 if (!total)
314 fprintf(stderr, "sql:%" PRId64 ".%03d opt:%" PRId64 ".%03d run:%" PRId64 ".%03d ",
315 sqloptimizer / 1000, (int) (sqloptimizer % 1000),
316 maloptimizer / 1000, (int) (maloptimizer % 1000),
317 querytime / 1000, (int) (querytime % 1000));
318 if (singleinstr != total)
319 fprintf(stderr, "clk:%" PRId64 ".%03d ", t / 1000, (int) (t % 1000));
320 fprintf(stderr, "ms\n");
321 fflush(stderr);
322 return;
323 }
324 return;
325}
326
327/* The Mapi library eats away the comment lines, which we need to
328 * detect end of debugging. We overload the routine to our liking. */
329
330static char *
331fetch_line(MapiHdl hdl)
332{
333 char *reply;
334
335 if ((reply = mapi_fetch_line(hdl)) == NULL)
336 return NULL;
337 if (strncmp(reply, "mdb>#", 5) == 0) {
338 if (strncmp(reply, "mdb>#EOD", 8) == 0)
339 setPrompt();
340 else
341 sprintf(promptbuf, "mdb>");
342 }
343 return reply;
344}
345
346static int
347fetch_row(MapiHdl hdl)
348{
349 char *reply;
350
351 do {
352 if ((reply = fetch_line(hdl)) == NULL)
353 return 0;
354 } while (*reply != '[' && *reply != '=');
355 return mapi_split_line(hdl);
356}
357
358static void
359SQLsetSpecial(const char *command)
360{
361 if (mode == SQL && command && specials == NOmodifier) {
362 /* catch the specials for better rendering */
363 while (*command == ' ' || *command == '\t')
364 command++;
365 if (strncmp(command, "debug", 5) == 0)
366 specials = DEBUGmodifier;
367 else
368 specials = NOmodifier;
369 }
370}
371
372/* return the display length of a UTF-8 string
373 if e is not NULL, return length up to e */
374static size_t
375utf8strlenmax(char *s, char *e, size_t max, char **t)
376{
377 size_t len = 0, len0 = 0;
378 int c;
379 int n;
380 char *t0 = s;
381
382 assert(max == 0 || t != NULL);
383 if (s == NULL)
384 return 0;
385 c = 0;
386 n = 0;
387 while (*s != 0 && (e == NULL || s < e)) {
388 if (*s == '\n') {
389 assert(n == 0);
390 if (max) {
391 *t = s;
392 return len;
393 }
394 len++;
395 n = 0;
396 } else if (*s == '\t') {
397 assert(n == 0);
398 len++; /* rendered as single space */
399 n = 0;
400 } else if ((unsigned char) *s <= 0x1F || *s == '\177') {
401 assert(n == 0);
402 len += 4;
403 n = 0;
404 } else if ((*s & 0x80) == 0) {
405 assert(n == 0);
406 len++;
407 n = 0;
408 } else if ((*s & 0xC0) == 0x80) {
409 c = (c << 6) | (*s & 0x3F);
410 if (--n == 0) {
411 /* last byte of a multi-byte character */
412 len++;
413 /* this list was created by combining
414 * the code points marked as
415 * Emoji_Presentation in
416 * /usr/share/unicode/emoji/emoji-data.txt
417 * and code points marked either F or
418 * W in EastAsianWidth.txt; this list
419 * is up-to-date with Unicode 11.0 */
420 if ((0x1100 <= c && c <= 0x115F) ||
421 (0x231A <= c && c <= 0x231B) ||
422 (0x2329 <= c && c <= 0x232A) ||
423 (0x23E9 <= c && c <= 0x23EC) ||
424 c == 0x23F0 ||
425 c == 0x23F3 ||
426 (0x25FD <= c && c <= 0x25FE) ||
427 (0x2614 <= c && c <= 0x2615) ||
428 (0x2648 <= c && c <= 0x2653) ||
429 c == 0x267F ||
430 c == 0x2693 ||
431 c == 0x26A1 ||
432 (0x26AA <= c && c <= 0x26AB) ||
433 (0x26BD <= c && c <= 0x26BE) ||
434 (0x26C4 <= c && c <= 0x26C5) ||
435 c == 0x26CE ||
436 c == 0x26D4 ||
437 c == 0x26EA ||
438 (0x26F2 <= c && c <= 0x26F3) ||
439 c == 0x26F5 ||
440 c == 0x26FA ||
441 c == 0x26FD ||
442 c == 0x2705 ||
443 (0x270A <= c && c <= 0x270B) ||
444 c == 0x2728 ||
445 c == 0x274C ||
446 c == 0x274E ||
447 (0x2753 <= c && c <= 0x2755) ||
448 c == 0x2757 ||
449 (0x2795 <= c && c <= 0x2797) ||
450 c == 0x27B0 ||
451 c == 0x27BF ||
452 (0x2B1B <= c && c <= 0x2B1C) ||
453 c == 0x2B50 ||
454 c == 0x2B55 ||
455 (0x2E80 <= c && c <= 0x2E99) ||
456 (0x2E9B <= c && c <= 0x2EF3) ||
457 (0x2F00 <= c && c <= 0x2FD5) ||
458 (0x2FF0 <= c && c <= 0x2FFB) ||
459 (0x3000 <= c && c <= 0x303E) ||
460 (0x3041 <= c && c <= 0x3096) ||
461 (0x3099 <= c && c <= 0x30FF) ||
462 (0x3105 <= c && c <= 0x312F) ||
463 (0x3131 <= c && c <= 0x318E) ||
464 (0x3190 <= c && c <= 0x31BA) ||
465 (0x31C0 <= c && c <= 0x31E3) ||
466 (0x31F0 <= c && c <= 0x321E) ||
467 (0x3220 <= c && c <= 0x3247) ||
468 (0x3250 <= c && c <= 0x32FE) ||
469 (0x3300 <= c && c <= 0x4DBF) ||
470 (0x4E00 <= c && c <= 0xA48C) ||
471 (0xA490 <= c && c <= 0xA4C6) ||
472 (0xA960 <= c && c <= 0xA97C) ||
473 (0xAC00 <= c && c <= 0xD7A3) ||
474 (0xF900 <= c && c <= 0xFAFF) ||
475 (0xFE10 <= c && c <= 0xFE19) ||
476 (0xFE30 <= c && c <= 0xFE52) ||
477 (0xFE54 <= c && c <= 0xFE66) ||
478 (0xFE68 <= c && c <= 0xFE6B) ||
479 (0xFF01 <= c && c <= 0xFF60) ||
480 (0xFFE0 <= c && c <= 0xFFE6) ||
481 (0x16FE0 <= c && c <= 0x16FE1) ||
482 (0x17000 <= c && c <= 0x187F1) ||
483 (0x18800 <= c && c <= 0x18AF2) ||
484 (0x1B000 <= c && c <= 0x1B11E) ||
485 (0x1B170 <= c && c <= 0x1B2FB) ||
486 c == 0x1F004 ||
487 c == 0x1F0CF ||
488 c == 0x1F18E ||
489 (0x1F191 <= c && c <= 0x1F19A) ||
490 (0x1F200 <= c && c <= 0x1F202) ||
491 (0x1F210 <= c && c <= 0x1F23B) ||
492 (0x1F240 <= c && c <= 0x1F248) ||
493 (0x1F250 <= c && c <= 0x1F251) ||
494 (0x1F260 <= c && c <= 0x1F265) ||
495 (0x1F300 <= c && c <= 0x1F320) ||
496 (0x1F32D <= c && c <= 0x1F335) ||
497 (0x1F337 <= c && c <= 0x1F37C) ||
498 (0x1F37E <= c && c <= 0x1F393) ||
499 (0x1F3A0 <= c && c <= 0x1F3CA) ||
500 (0x1F3CF <= c && c <= 0x1F3D3) ||
501 (0x1F3E0 <= c && c <= 0x1F3F0) ||
502 c == 0x1F3F4 ||
503 (0x1F3F8 <= c && c <= 0x1F43E) ||
504 c == 0x1F440 ||
505 (0x1F442 <= c && c <= 0x1F4FC) ||
506 (0x1F4FF <= c && c <= 0x1F53D) ||
507 (0x1F54B <= c && c <= 0x1F54E) ||
508 (0x1F550 <= c && c <= 0x1F567) ||
509 c == 0x1F57A ||
510 (0x1F595 <= c && c <= 0x1F596) ||
511 c == 0x1F5A4 ||
512 (0x1F5FB <= c && c <= 0x1F64F) ||
513 (0x1F680 <= c && c <= 0x1F6C5) ||
514 c == 0x1F6CC ||
515 (0x1F6D0 <= c && c <= 0x1F6D2) ||
516 (0x1F6EB <= c && c <= 0x1F6EC) ||
517 (0x1F6F4 <= c && c <= 0x1F6F9) ||
518 (0x1F910 <= c && c <= 0x1F93E) ||
519 (0x1F940 <= c && c <= 0x1F970) ||
520 (0x1F973 <= c && c <= 0x1F976) ||
521 c == 0x1F97A ||
522 (0x1F97C <= c && c <= 0x1F9A2) ||
523 (0x1F9B0 <= c && c <= 0x1F9B9) ||
524 (0x1F9C0 <= c && c <= 0x1F9C2) ||
525 (0x1F9D0 <= c && c <= 0x1F9FF) ||
526 (0x20000 <= c && c <= 0x2FFFD) ||
527 (0x30000 <= c && c <= 0x3FFFD))
528 len++;
529 else if (0x0080 <= c && c <= 0x009F)
530 len += 5;
531
532 }
533 } else if ((*s & 0xE0) == 0xC0) {
534 assert(n == 0);
535 n = 1;
536 c = *s & 0x1F;
537 } else if ((*s & 0xF0) == 0xE0) {
538 assert(n == 0);
539 n = 2;
540 c = *s & 0x0F;
541 } else if ((*s & 0xF8) == 0xF0) {
542 assert(n == 0);
543 n = 3;
544 c = *s & 0x07;
545 } else if ((*s & 0xFC) == 0xF8) {
546 assert(n == 0);
547 n = 4;
548 c = *s & 0x03;
549 } else {
550 assert(0);
551 n = 0;
552 }
553 s++;
554 if (n == 0) {
555 if (max != 0) {
556 if (len > max) {
557 *t = t0;
558 return len0;
559 }
560 if (len == max) {
561 *t = s;
562 return len;
563 }
564 }
565 t0 = s;
566 len0 = len;
567 }
568 }
569 if (max != 0)
570 *t = s;
571 return len;
572}
573
574static size_t
575utf8strlen(char *s, char *e)
576{
577 return utf8strlenmax(s, e, 0, NULL);
578}
579
580/* skip the specified number of UTF-8 characters, but stop at a newline */
581static char *
582utf8skip(char *s, size_t i)
583{
584 utf8strlenmax(s, NULL, i, &s);
585 return s;
586}
587
588static int
589SQLrow(int *len, int *numeric, char **rest, int fields, int trim, char wm)
590{
591 int i;
592 bool more, first = true;
593 char *t;
594 int rows = 0; /* return number of output lines printed */
595 size_t ulen;
596 int *cutafter = malloc(sizeof(int) * fields);
597
598 if (cutafter == NULL) {
599 fprintf(stderr,"Malloc for SQLrow failed");
600 exit(2);
601 }
602 /* trim the text if needed */
603 if (trim == 1) {
604 for (i = 0; i < fields; i++) {
605 if ((t = rest[i]) != NULL &&
606 utf8strlen(t, NULL) > (size_t) len[i]) {
607 /* eat leading whitespace */
608 while (*t != 0 && my_isspace(*t))
609 t++;
610 rest[i] = t;
611 }
612 }
613 }
614
615 for (i = 0; i < fields; i++)
616 cutafter[i] = -1;
617
618 do {
619 more = false;
620 for (i = 0; i < fields; i++) {
621 if (rest[i] == NULL || *rest[i] == 0) {
622 mnstr_printf(toConsole, "%c %*s ",
623 first ? '|' : i > 0 && cutafter[i - 1] == 0 ? '>' : ':',
624 len[i], "");
625 } else {
626 ulen = utf8strlen(rest[i], NULL);
627
628 if (first && trim == 2) {
629 /* calculate the height of
630 * this field according to the
631 * golden ratio, with a
632 * correction for a terminal
633 * screen (1.62 * 2 -> 3 :
634 * 9.72~10) */
635 if (ulen > (size_t) len[i]) {
636 cutafter[i] = 3 * len[i] / 10;
637 if (cutafter[i] == 1)
638 cutafter[i]++;
639 }
640 }
641
642 /* on each cycle we get closer to the limit */
643 if (cutafter[i] >= 0)
644 cutafter[i]--;
645
646 /* break the string into pieces and
647 * left-adjust them in the column */
648 t = strchr(rest[i], '\n');
649 if (ulen > (size_t) len[i] || t) {
650 char *s;
651
652 t = utf8skip(rest[i], len[i]);
653 if (trim == 1) {
654 while (t > rest[i] && !my_isspace(*t))
655 while ((*--t & 0xC0) == 0x80)
656 ;
657 if (t == rest[i] && !my_isspace(*t))
658 t = utf8skip(rest[i], len[i]);
659 }
660 mnstr_printf(toConsole, "%c",
661 first ? '|' : i > 0 && cutafter[i - 1] == 0 ? '>' : ':');
662 if (numeric[i])
663 mnstr_printf(toConsole, "%*s",
664 (int) (len[i] - (ulen - utf8strlen(t, NULL))),
665 "");
666
667 s = t;
668 if (trim == 1)
669 while (my_isspace(*s))
670 s++;
671 if (trim == 2 && *s == '\n')
672 s++;
673 if (*s && cutafter[i] == 0) {
674 t = utf8skip(rest[i], len[i] - 2);
675 s = t;
676 if (trim == 1)
677 while (my_isspace(*s))
678 s++;
679 if (trim == 2 && *s == '\n')
680 s++;
681 mnstr_write(toConsole, " ", 1, 1);
682 for (char *p = rest[i]; p < t; p++) {
683 if (*p == '\t')
684 mnstr_write(toConsole, " ", 1, 1);
685 else if ((unsigned char) *p <= 0x1F || *p == '\177')
686 mnstr_printf(toConsole, "\\%03o", (unsigned char) *p);
687 else if (*p == '\302' &&
688 (p[1] & 0xE0) == 0x80) {
689 mnstr_printf(toConsole, "\\u%04x", (p[1] & 0x3F) | 0x80);
690 p++;
691 } else
692 mnstr_write(toConsole, p, 1, 1);
693 }
694 mnstr_printf(toConsole, "...%*s",
695 len[i] - 2 - (int) utf8strlen(rest[i], t),
696 "");
697 croppedfields++;
698 } else {
699 mnstr_write(toConsole, " ", 1, 1);
700 for (char *p = rest[i]; p < t; p++) {
701 if (*p == '\t')
702 mnstr_write(toConsole, " ", 1, 1);
703 else if ((unsigned char) *p <= 0x1F || *p == '\177')
704 mnstr_printf(toConsole, "\\%03o", (unsigned char) *p);
705 else if (*p == '\302' &&
706 (p[1] & 0xE0) == 0x80) {
707 mnstr_printf(toConsole, "\\u%04x", (p[1] & 0x3F) | 0x80);
708 p++;
709 } else
710 mnstr_write(toConsole, p, 1, 1);
711 }
712 mnstr_write(toConsole, " ", 1, 1);
713 if (!numeric[i])
714 mnstr_printf(toConsole, "%*s",
715 (int) (len[i] - (ulen - utf8strlen(t, NULL))),
716 "");
717 }
718 rest[i] = *s ? s : 0;
719 if (rest[i] == NULL) {
720 /* avoid > as border
721 * marker if
722 * everything actually
723 * just fits */
724 cutafter[i] = -1;
725 }
726 if (cutafter[i] == 0)
727 rest[i] = NULL;
728 if (rest[i])
729 more = true;
730 } else {
731 mnstr_printf(toConsole, "%c",
732 first ? '|' : i > 0 && cutafter[i - 1] == 0 ? '>' : ':');
733 if (numeric[i]) {
734 mnstr_printf(toConsole, "%*s",
735 (int) (len[i] - ulen),
736 "");
737 mnstr_printf(toConsole, " %s ",
738 rest[i]);
739 }
740 if (!numeric[i]) {
741 char *p;
742 /* replace tabs with a
743 * single space to
744 * avoid screwup the
745 * width
746 * calculations */
747 mnstr_write(toConsole, " ", 1, 1);
748 for (p = rest[i]; *p; p++) {
749 if (*p == '\t')
750 mnstr_write(toConsole, " ", 1, 1);
751 else if ((unsigned char) *p <= 0x1F || *p == '\177')
752 mnstr_printf(toConsole, "\\%03o", (unsigned char) *p);
753 else if (*p == '\302' &&
754 (p[1] & 0xE0) == 0x80) {
755 mnstr_printf(toConsole, "\\u%04x", (p[1] & 0x3F) | 0x80);
756 p++;
757 } else
758 mnstr_write(toConsole, p, 1, 1);
759 }
760 mnstr_printf(toConsole, " %*s",
761 (int) (len[i] - ulen),
762 "");
763 }
764 rest[i] = 0;
765 /* avoid > as border marker if
766 * everything actually just
767 * fits */
768 if (cutafter[i] == 0)
769 cutafter[i] = -1;
770 }
771 }
772 }
773 mnstr_printf(toConsole, "%c%s\n",
774 first ? '|' : i > 0 && cutafter[i - 1] == 0 ? '>' : ':',
775 wm ? ">" : "");
776 first = false;
777 rows++;
778 } while (more);
779
780 free(cutafter);
781 return rows;
782}
783
784static void
785XMLprdata(const char *val)
786{
787 if (val == NULL)
788 return;
789 while (*val) {
790 if (*val == '&')
791 mnstr_printf(toConsole, "&amp;");
792 else if (*val == '<')
793 mnstr_printf(toConsole, "&lt;");
794 else if (*val == '>')
795 mnstr_printf(toConsole, "&gt;");
796 else if (*val == '"')
797 mnstr_printf(toConsole, "&quot;");
798 else if (*val == '\'')
799 mnstr_printf(toConsole, "&apos;");
800 else if ((*val & 0xFF) < 0x20) /* control character */
801 mnstr_printf(toConsole, "&#%d;", *val & 0xFF);
802 else if ((*val & 0x80) != 0 /* && encoding != NULL */ ) {
803 int n;
804 unsigned int m;
805 unsigned int c = *val & 0x7F;
806
807 for (n = 0, m = 0x40; c & m; n++, m >>= 1)
808 c &= ~m;
809 while (--n >= 0)
810 c = (c << 6) | (*++val & 0x3F);
811 mnstr_printf(toConsole, "&#x%x;", c);
812 } else
813 mnstr_write(toConsole, val, 1, 1);
814 val++;
815 }
816}
817
818static void
819XMLprattr(const char *name, const char *val)
820{
821 mnstr_printf(toConsole, " %s=\"", name);
822 XMLprdata(val);
823 mnstr_write(toConsole, "\"", 1, 1);
824}
825
826static void
827XMLrenderer(MapiHdl hdl)
828{
829 int i, fields;
830 char *name;
831
832 /* we must use toConsole since the XML file is encoded in UTF-8 */
833 mnstr_flush(toConsole);
834 mnstr_printf(toConsole, "<?xml version='1.0' encoding='UTF-8'?>\n");
835 mnstr_printf(toConsole,
836 "<!DOCTYPE table [\n"
837 " <!ELEMENT table (row)*>\n" /* a table consists of zero or more rows */
838 " <!ELEMENT row (column)+>\n" /* a row consists of one or more columns */
839 " <!ELEMENT column (#PCDATA)>\n"
840 " <!ATTLIST table name CDATA #IMPLIED>\n" /* a table may have a name */
841 " <!ATTLIST column name CDATA #IMPLIED\n" /* a column may have a name */
842 " isnull (true|false) 'false'>]>\n");
843 mnstr_printf(toConsole, "<table");
844 name = mapi_get_table(hdl, 0);
845 if (name != NULL && *name != 0)
846 XMLprattr("name", name);
847 mnstr_printf(toConsole, ">\n");
848 while (!mnstr_errnr(toConsole) && (fields = fetch_row(hdl)) != 0) {
849 mnstr_printf(toConsole, "<row>");
850 for (i = 0; i < fields; i++) {
851 char *data = mapi_fetch_field(hdl, i);
852
853 mnstr_printf(toConsole, "<column");
854 name = mapi_get_name(hdl, i);
855 if (name != NULL && *name != 0)
856 XMLprattr("name", name);
857 if (data == NULL) {
858 XMLprattr("isnull", "true");
859 mnstr_write(toConsole, "/", 1, 1);
860 }
861 mnstr_write(toConsole, ">", 1, 1);
862 if (data) {
863 XMLprdata(data);
864 mnstr_printf(toConsole, "</column>");
865 }
866 }
867 mnstr_printf(toConsole, "</row>\n");
868 }
869 mnstr_printf(toConsole, "</table>\n");
870 mnstr_flush(toConsole);
871}
872
873static void
874EXPANDEDrenderer(MapiHdl hdl)
875{
876 int i, fields, fieldw, rec = 0;
877
878 fields = mapi_get_field_count(hdl);
879 fieldw = 0;
880 for (i = 0; i < fields; i++) {
881 int w = (int) utf8strlen(mapi_get_name(hdl, i), NULL);
882 if (w > fieldw)
883 fieldw = w;
884 }
885 while (!mnstr_errnr(toConsole) && (fields = fetch_row(hdl)) != 0) {
886 int valuew = 0, len;
887 ++rec;
888 for (i = 0; i < fields; i++) {
889 char *data = mapi_fetch_field(hdl, i);
890 char *edata;
891 int w;
892
893 if (data == NULL)
894 data = nullstring;
895 do {
896 edata = utf8skip(data, ~(size_t)0);
897 w = (int) utf8strlen(data, edata);
898 if (w > valuew)
899 valuew = w;
900 data = edata;
901 if (*data)
902 data++;
903 } while (*edata);
904 }
905 len = mnstr_printf(toConsole, "-[ RECORD %d ]-", rec);
906 while (len++ < fieldw + valuew + 3)
907 mnstr_write(toConsole, "-", 1, 1);
908 mnstr_write(toConsole, "\n", 1, 1);
909 for (i = 0; i < fields; i++) {
910 char *data = mapi_fetch_field(hdl, i);
911 char *edata;
912 const char *name = mapi_get_name(hdl, i);
913 if (data == NULL)
914 data = nullstring;
915 do {
916 edata = utf8skip(data, ~(size_t)0);
917 mnstr_printf(toConsole, "%-*s | %.*s\n", fieldw, name, (int) (edata - data), data);
918 name = "";
919 data = edata;
920 if (*data)
921 data++;
922 } while (*edata);
923 }
924 }
925 mnstr_flush(toConsole);
926}
927
928static void
929CSVrenderer(MapiHdl hdl)
930{
931 int fields;
932 const char *s;
933 const char specials[] = {'"', '\\', '\n', '\r', '\t', *separator, '\0'};
934 int i;
935
936 if (csvheader) {
937 fields = mapi_get_field_count(hdl);
938 for (i = 0; i < fields; i++) {
939 s = mapi_get_name(hdl, i);
940 if (s == NULL)
941 s = "";
942 mnstr_printf(toConsole, "%s%s", i == 0 ? "" : separator, s);
943 }
944 mnstr_printf(toConsole, "\n");
945 }
946 while (!mnstr_errnr(toConsole) && (fields = fetch_row(hdl)) != 0) {
947 for (i = 0; i < fields; i++) {
948 s = mapi_fetch_field(hdl, i);
949 if (s != NULL && s[strcspn(s, specials)] != '\0') {
950 mnstr_printf(toConsole, "%s\"",
951 i == 0 ? "" : separator);
952 while (*s) {
953 switch (*s) {
954 case '\n':
955 mnstr_write(toConsole, "\\n", 1, 2);
956 break;
957 case '\t':
958 mnstr_write(toConsole, "\\t", 1, 2);
959 break;
960 case '\r':
961 mnstr_write(toConsole, "\\r", 1, 2);
962 break;
963 case '\\':
964 mnstr_write(toConsole, "\\\\", 1, 2);
965 break;
966 case '"':
967 mnstr_write(toConsole, "\"\"", 1, 2);
968 break;
969 default:
970 mnstr_write(toConsole, s, 1, 1);
971 break;
972 }
973 s++;
974 }
975 mnstr_write(toConsole, "\"", 1, 1);
976 } else {
977 if (s == NULL)
978 s = nullstring == default_nullstring ? "" : nullstring;
979 mnstr_printf(toConsole, "%s%s",
980 i == 0 ? "" : separator, s);
981 }
982 }
983 mnstr_printf(toConsole, "\n");
984 }
985}
986
987static void
988SQLseparator(int *len, int fields, char sep)
989{
990 int i, j;
991
992 mnstr_printf(toConsole, "+");
993 for (i = 0; i < fields; i++) {
994 mnstr_printf(toConsole, "%c", sep);
995 for (j = 0; j < (len[i] < 0 ? -len[i] : len[i]); j++)
996 mnstr_printf(toConsole, "%c", sep);
997 mnstr_printf(toConsole, "%c+", sep);
998 }
999 mnstr_printf(toConsole, "\n");
1000}
1001
1002static void
1003SQLqueryEcho(MapiHdl hdl)
1004{
1005 if (echoquery) {
1006 char *qry;
1007
1008 qry = mapi_get_query(hdl);
1009 if (qry) {
1010 if (formatter != TABLEformatter) {
1011 char *p = qry;
1012 char *q = p;
1013 while ((q = strchr(q, '\n')) != NULL) {
1014 *q++ = '\0';
1015 mnstr_printf(toConsole, "#%s\n", p);
1016 p = q;
1017 }
1018 if (*p) {
1019 /* query does not end in \n */
1020 mnstr_printf(toConsole, "#%s\n", p);
1021 }
1022 } else {
1023 size_t qrylen = strlen(qry);
1024
1025 mnstr_printf(toConsole, "%s", qry);
1026 if (qrylen > 0 && qry[qrylen - 1] != '\n') {
1027 /* query does not end in \n */
1028 mnstr_printf(toConsole, "\n");
1029 }
1030 }
1031 free(qry);
1032 }
1033 }
1034}
1035
1036/* state machine to recognize integers, floating point numbers, OIDs */
1037static char *
1038classify(const char *s, size_t l)
1039{
1040 /* state is the current state of the state machine:
1041 * 0 - initial state, no input seen
1042 * 1 - initial sign
1043 * 2 - valid integer (optionally preceded by a sign)
1044 * 3 - valid integer, followed by a decimal point
1045 * 4 - fixed point number of the form [sign] digits period digits
1046 * 5 - exponent marker after integer or fixed point number
1047 * 6 - sign after exponent marker
1048 * 7 - valid floating point number with exponent
1049 * 8 - integer followed by single 'L'
1050 * 9 - integer followed by 'LL' (lng)
1051 * 10 - fixed or floating point number followed by single 'L'
1052 * 11 - fixed or floating point number followed by 'LL' (dbl)
1053 * 12 - integer followed by '@'
1054 * 13 - valid OID (integer followed by '@0')
1055 */
1056 int state = 0;
1057
1058 if ((l == 4 && strcmp(s, "true") == 0) ||
1059 (l == 5 && strcmp(s, "false") == 0))
1060 return "bit";
1061 while (l != 0) {
1062 if (*s == 0)
1063 return "str";
1064 switch (*s) {
1065 case '0':
1066 if (state == 12) {
1067 state = 13; /* int + '@0' (oid) */
1068 break;
1069 }
1070 /* fall through */
1071 case '1':
1072 case '2':
1073 case '3':
1074 case '4':
1075 case '5':
1076 case '6':
1077 case '7':
1078 case '8':
1079 case '9':
1080 switch (state) {
1081 case 0:
1082 case 1:
1083 state = 2; /* digit after optional sign */
1084 break;
1085 case 3:
1086 state = 4; /* digit after decimal point */
1087 break;
1088 case 5:
1089 case 6:
1090 state = 7; /* digit after exponent marker and optional sign */
1091 break;
1092 case 2:
1093 case 4:
1094 case 7:
1095 break; /* more digits */
1096 default:
1097 return "str";
1098 }
1099 break;
1100 case '.':
1101 if (state == 2)
1102 state = 3; /* decimal point */
1103 else
1104 return "str";
1105 break;
1106 case 'e':
1107 case 'E':
1108 if (state == 2 || state == 4)
1109 state = 5; /* exponent marker */
1110 else
1111 return "str";
1112 break;
1113 case '+':
1114 case '-':
1115 if (state == 0)
1116 state = 1; /* sign at start */
1117 else if (state == 5)
1118 state = 6; /* sign after exponent marker */
1119 else
1120 return "str";
1121 break;
1122 case '@':
1123 if (state == 2)
1124 state = 12; /* OID marker */
1125 else
1126 return "str";
1127 break;
1128 case 'L':
1129 switch (state) {
1130 case 2:
1131 state = 8; /* int + 'L' */
1132 break;
1133 case 8:
1134 state = 9; /* int + 'LL' */
1135 break;
1136 case 4:
1137 case 7:
1138 state = 10; /* dbl + 'L' */
1139 break;
1140 case 10:
1141 state = 11; /* dbl + 'LL' */
1142 break;
1143 default:
1144 return "str";
1145 }
1146 break;
1147 default:
1148 return "str";
1149 }
1150 s++;
1151 l--;
1152 }
1153 switch (state) {
1154 case 13:
1155 return "oid";
1156 case 2:
1157 return "int";
1158 case 4:
1159 case 7:
1160 case 11:
1161 return "dbl";
1162 case 9:
1163 return "lng";
1164 default:
1165 return "str";
1166 }
1167}
1168
1169static void
1170TESTrenderer(MapiHdl hdl)
1171{
1172 int fields;
1173 char *reply;
1174 char *s;
1175 size_t l;
1176 char *tp;
1177 char *sep;
1178 int i;
1179
1180 while (!mnstr_errnr(toConsole) && (reply = fetch_line(hdl)) != 0) {
1181 if (*reply != '[') {
1182 if (*reply == '=')
1183 reply++;
1184 mnstr_printf(toConsole, "%s\n", reply);
1185 continue;
1186 }
1187 fields = mapi_split_line(hdl);
1188 sep = "[ ";
1189 for (i = 0; i < fields; i++) {
1190 s = mapi_fetch_field(hdl, i);
1191 l = mapi_fetch_field_len(hdl, i);
1192 tp = mapi_get_type(hdl, i);
1193 if (strcmp(tp, "unknown") == 0)
1194 tp = classify(s, l);
1195 mnstr_printf(toConsole, "%s", sep);
1196 sep = ",\t";
1197 if (s == NULL)
1198 mnstr_printf(toConsole, "%s", mode == SQL ? "NULL" : "nil");
1199 else if (strcmp(tp, "varchar") == 0 ||
1200 strcmp(tp, "char") == 0 ||
1201 strcmp(tp, "clob") == 0 ||
1202 strcmp(tp, "str") == 0 ||
1203 strcmp(tp, "json") == 0 ||
1204 /* NULL byte in string? */
1205 strlen(s) < l ||
1206 /* start or end with white space? */
1207 my_isspace(*s) ||
1208 (l > 0 && my_isspace(s[l - 1])) ||
1209 /* timezone can have embedded comma */
1210 strcmp(tp, "timezone") == 0 ||
1211 /* a bunch of geom types */
1212 strcmp(tp, "curve") == 0 ||
1213 strcmp(tp, "geometry") == 0 ||
1214 strcmp(tp, "linestring") == 0 ||
1215 strcmp(tp, "mbr") == 0 ||
1216 strcmp(tp, "multilinestring") == 0 ||
1217 strcmp(tp, "point") == 0 ||
1218 strcmp(tp, "polygon") == 0 ||
1219 strcmp(tp, "surface") == 0) {
1220 mnstr_printf(toConsole, "\"");
1221 while (l != 0) {
1222 switch (*s) {
1223 case '\n':
1224 mnstr_write(toConsole, "\\n", 1, 2);
1225 break;
1226 case '\t':
1227 mnstr_write(toConsole, "\\t", 1, 2);
1228 break;
1229 case '\r':
1230 mnstr_write(toConsole, "\\r", 1, 2);
1231 break;
1232 case '\\':
1233 mnstr_write(toConsole, "\\\\", 1, 2);
1234 break;
1235 case '"':
1236 mnstr_write(toConsole, "\\\"", 1, 2);
1237 break;
1238 case '0':
1239 case '1':
1240 case '2':
1241 case '3':
1242 case '4':
1243 case '5':
1244 case '6':
1245 case '7':
1246 case '8':
1247 case '9':
1248 if (strcmp(tp, "curve") == 0 ||
1249 strcmp(tp, "geometry") == 0 ||
1250 strcmp(tp, "linestring") == 0 ||
1251 strcmp(tp, "mbr") == 0 ||
1252 strcmp(tp, "multilinestring") == 0 ||
1253 strcmp(tp, "point") == 0 ||
1254 strcmp(tp, "polygon") == 0 ||
1255 strcmp(tp, "surface") == 0) {
1256 char *e;
1257 double d;
1258 d = strtod(s, &e);
1259 if (s != e) {
1260 mnstr_printf(toConsole, "%.10g", d);
1261 l -= e - s;
1262 s = e;
1263 continue;
1264 }
1265 }
1266 /* fall through */
1267 default:
1268 if ((unsigned char) *s < ' ')
1269 mnstr_printf(toConsole,
1270 "\\%03o",
1271 (int) (unsigned char) *s);
1272 else
1273 mnstr_write(toConsole, s, 1, 1);
1274 break;
1275 }
1276 s++;
1277 l--;
1278 }
1279 mnstr_write(toConsole, "\"", 1, 1);
1280 } else if (strcmp(tp, "double") == 0 ||
1281 strcmp(tp, "dbl") == 0) {
1282 char buf[32];
1283 int j;
1284 double v;
1285 if (strcmp(s, "-0") == 0) /* normalize -0 */
1286 s = "0";
1287 v = strtod(s, NULL);
1288 for (j = 4; j < 11; j++) {
1289 snprintf(buf, sizeof(buf), "%.*g", j, v);
1290 if (v == strtod(buf, NULL))
1291 break;
1292 }
1293 mnstr_printf(toConsole, "%s", buf);
1294 } else if (strcmp(tp, "real") == 0) {
1295 char buf[32];
1296 int j;
1297 float v;
1298 if (strcmp(s, "-0") == 0) /* normalize -0 */
1299 s = "0";
1300 v = strtof(s, NULL);
1301 for (j = 4; j < 6; j++) {
1302 snprintf(buf, sizeof(buf), "%.*g", j, v);
1303 if (v == strtof(buf, NULL))
1304 break;
1305 }
1306 mnstr_printf(toConsole, "%s", buf);
1307 } else
1308 mnstr_printf(toConsole, "%s", s);
1309 }
1310 mnstr_printf(toConsole, "\t]\n");
1311 }
1312}
1313
1314static void
1315RAWrenderer(MapiHdl hdl)
1316{
1317 char *line;
1318
1319 while ((line = fetch_line(hdl)) != 0) {
1320 if (*line == '=')
1321 line++;
1322 mnstr_printf(toConsole, "%s\n", line);
1323 }
1324}
1325
1326static void
1327SAMrenderer(MapiHdl hdl)
1328{
1329 /* Variables keeping track of which result set fields map to
1330 * qname, flag etc. (-1 means that it does not occur in result
1331 * set) */
1332 int field_qname = -1;
1333 int field_flag = -1;
1334 int field_rname = -1;
1335 int field_pos = -1;
1336 int field_mapq = -1;
1337 int field_cigar = -1;
1338 int field_rnext = -1;
1339 int field_pnext = -1;
1340 int field_tlen = -1;
1341 int field_seq = -1;
1342 int field_qual = -1;
1343
1344 int field_count = mapi_get_field_count(hdl);
1345 int t_fields;
1346
1347 int i;
1348
1349 /* First, initialize field variables properly */
1350 for (i = 0; i < field_count; i++) {
1351 char *field_name = mapi_get_name(hdl, i);
1352 if (strcmp(field_name, "qname") == 0)
1353 field_qname = i;
1354 else if (strcmp(field_name, "flag" ) == 0)
1355 field_flag = i;
1356 else if (strcmp(field_name, "rname") == 0)
1357 field_rname = i;
1358 else if (strcmp(field_name, "pos" ) == 0)
1359 field_pos = i;
1360 else if (strcmp(field_name, "mapq" ) == 0)
1361 field_mapq = i;
1362 else if (strcmp(field_name, "cigar") == 0)
1363 field_cigar = i;
1364 else if (strcmp(field_name, "rnext") == 0)
1365 field_rnext = i;
1366 else if (strcmp(field_name, "pnext") == 0)
1367 field_pnext = i;
1368 else if (strcmp(field_name, "tlen" ) == 0)
1369 field_tlen = i;
1370 else if (strcmp(field_name, "seq" ) == 0)
1371 field_seq = i;
1372 else if (strcmp(field_name, "qual" ) == 0)
1373 field_qual = i;
1374 else
1375 mnstr_printf(stderr_stream, "Unexpected column name in result set: '%s'. Data in this column is not used.\n", field_name);
1376 }
1377
1378 /* Write all alignments */
1379 while (!mnstr_errnr(toConsole) && (t_fields = fetch_row(hdl)) != 0) {
1380 if (t_fields != field_count) {
1381 mnstr_printf(stderr_stream,
1382 "invalid tuple received from server, "
1383 "got %d columns, expected %d, ignoring\n", t_fields, field_count);
1384 continue;
1385 }
1386
1387 /* Write fields to SAM line */
1388 mnstr_printf(toConsole, "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
1389 (field_qname == -1 ? "*" : mapi_fetch_field(hdl, field_qname)),
1390 (field_flag == -1 ? "0" : mapi_fetch_field(hdl, field_flag )),
1391 (field_rname == -1 ? "*" : mapi_fetch_field(hdl, field_rname)),
1392 (field_pos == -1 ? "0" : mapi_fetch_field(hdl, field_pos )),
1393 (field_mapq == -1 ? "255" : mapi_fetch_field(hdl, field_mapq )),
1394 (field_cigar == -1 ? "*" : mapi_fetch_field(hdl, field_cigar)),
1395 (field_rnext == -1 ? "*" : mapi_fetch_field(hdl, field_rnext)),
1396 (field_pnext == -1 ? "0" : mapi_fetch_field(hdl, field_pnext)),
1397 (field_tlen == -1 ? "0" : mapi_fetch_field(hdl, field_tlen )),
1398 (field_seq == -1 ? "*" : mapi_fetch_field(hdl, field_seq )),
1399 (field_qual == -1 ? "*" : mapi_fetch_field(hdl, field_qual)));
1400 }
1401}
1402
1403static void
1404SQLheader(MapiHdl hdl, int *len, int fields, char more)
1405{
1406 SQLseparator(len, fields, '-');
1407 if (mapi_get_name(hdl, 0)) {
1408 int i;
1409 char **names = (char **) malloc(fields * sizeof(char *));
1410 int *numeric = (int *) malloc(fields * sizeof(int));
1411
1412 if (names == NULL || numeric == NULL) {
1413 free(names);
1414 free(numeric);
1415 fprintf(stderr,"Malloc for SQLheader failed");
1416 exit(2);
1417 }
1418 for (i = 0; i < fields; i++) {
1419 names[i] = mapi_get_name(hdl, i);
1420 numeric[i] = 0;
1421 }
1422 SQLrow(len, numeric, names, fields, 1, more);
1423 SQLseparator(len, fields, '=');
1424 free(names);
1425 free(numeric);
1426 }
1427}
1428
1429static void
1430SQLdebugRendering(MapiHdl hdl)
1431{
1432 char *reply;
1433 int cnt = 0;
1434
1435 sprintf(promptbuf, "mdb>");
1436 while ((reply = fetch_line(hdl))) {
1437 cnt++;
1438 mnstr_printf(toConsole, "%s\n", reply);
1439 if (strncmp(reply, "mdb>#EOD", 8) == 0) {
1440 cnt = 0;
1441 while ((reply = fetch_line(hdl)))
1442 mnstr_printf(toConsole, "%s\n", reply);
1443 break;
1444 }
1445 }
1446 if (cnt == 0) {
1447 setPrompt();
1448 specials = NOmodifier;
1449 }
1450}
1451
1452static void
1453SQLpagemove(int *len, int fields, int *ps, bool *silent)
1454{
1455 char buf[512];
1456 ssize_t sz;
1457
1458 SQLseparator(len, fields, '-');
1459 mnstr_printf(toConsole, "next page? (continue,quit,next)");
1460 mnstr_flush(toConsole);
1461 sz = mnstr_readline(fromConsole, buf, sizeof(buf));
1462 if (sz > 0) {
1463 if (buf[0] == 'c')
1464 *ps = 0;
1465 if (buf[0] == 'q')
1466 *silent = true;
1467 while (sz > 0 && buf[sz - 1] != '\n')
1468 sz = mnstr_readline(fromConsole, buf, sizeof(buf));
1469 }
1470 if (!*silent)
1471 SQLseparator(len, fields, '-');
1472}
1473
1474static void
1475SQLrenderer(MapiHdl hdl)
1476{
1477 int i, total, lentotal, vartotal, minvartotal;
1478 int fields, rfields, printfields = 0, max = 1, graphwaste = 0;
1479 int *len = NULL, *hdr = NULL, *numeric = NULL;
1480 char **rest = NULL;
1481 char buf[50];
1482 int ps = rowsperpage;
1483 bool silent = false;
1484 int64_t rows = 0;
1485
1486 croppedfields = 0;
1487 fields = mapi_get_field_count(hdl);
1488 rows = mapi_get_row_count(hdl);
1489
1490 len = calloc(fields, sizeof(*len));
1491 hdr = calloc(fields, sizeof(*hdr));
1492 rest = calloc(fields, sizeof(*rest));
1493 numeric = calloc(fields, sizeof(*numeric));
1494 if (len == NULL || hdr == NULL || rest == NULL || numeric == NULL) {
1495 if (len)
1496 free(len);
1497 if (hdr)
1498 free(hdr);
1499 if (rest)
1500 free(rest);
1501 if (numeric)
1502 free(numeric);
1503 fprintf(stderr,"Malloc for SQLrenderer failed");
1504 exit(2);
1505 }
1506
1507 total = 0;
1508 lentotal = 0;
1509 vartotal = 0;
1510 minvartotal = 0;
1511 for (i = 0; i < fields; i++) {
1512 char *s;
1513
1514 len[i] = mapi_get_len(hdl, i);
1515 if (len[i] == 0) {
1516 if ((s = mapi_get_type(hdl, i)) == NULL ||
1517 (strcmp(s, "varchar") != 0 &&
1518 strcmp(s, "clob") != 0 &&
1519 strcmp(s, "char") != 0 &&
1520 strcmp(s, "str") != 0 &&
1521 strcmp(s, "json") != 0)) {
1522 /* no table width known, use maximum,
1523 * rely on squeezing later on to fix
1524 * it to whatever is available; note
1525 * that for a column type of varchar,
1526 * 0 means the complete column is NULL
1527 * or empty string, so MINCOLSIZE
1528 * (below) will work great */
1529 len[i] = pagewidth <= 0 ? DEFWIDTH : pagewidth;
1530 } else if (strcmp(s, "uuid") == 0) {
1531 /* we know how large the UUID representation
1532 * is, even if the server doesn't */
1533 len[i] = 36;
1534 }
1535 }
1536 if (len[i] < MINCOLSIZE)
1537 len[i] = MINCOLSIZE;
1538 s = mapi_get_name(hdl, i);
1539 if (s != NULL) {
1540 size_t l = strlen(s);
1541 assert(l <= INT_MAX);
1542 hdr[i] = (int) l;
1543 } else {
1544 hdr[i] = 0;
1545 }
1546 /* if no rows, just try to draw headers nicely */
1547 if (rows == 0)
1548 len[i] = hdr[i];
1549 s = mapi_get_type(hdl, i);
1550 numeric[i] = s != NULL &&
1551 (strcmp(s, "int") == 0 ||
1552 strcmp(s, "tinyint") == 0 ||
1553 strcmp(s, "bigint") == 0 ||
1554 strcmp(s, "hugeint") == 0 ||
1555 strcmp(s, "oid") == 0 ||
1556 strcmp(s, "smallint") == 0 ||
1557 strcmp(s, "double") == 0 ||
1558 strcmp(s, "float") == 0 ||
1559 strcmp(s, "decimal") == 0);
1560
1561 if (rows == 0) {
1562 minvartotal += len[i]; /* don't wrap column headers if no data */
1563 } else if (numeric[i]) {
1564 /* minimum size is equal to maximum size */
1565 minvartotal += len[i];
1566 } else {
1567 /* minimum size for wide columns is MINVARCOLSIZE */
1568 minvartotal += len[i] > MINVARCOLSIZE ? MINVARCOLSIZE : len[i];
1569 }
1570 vartotal += len[i];
1571 total += len[i];
1572
1573 /* do a very pessimistic calculation to determine if more
1574 * columns would actually fit on the screen */
1575 if (pagewidth > 0 &&
1576 ((((printfields + 1) * 3) - 1) + 2) + /* graphwaste */
1577 (total - vartotal) + minvartotal > pagewidth) {
1578 /* this last column was too much */
1579 total -= len[i];
1580 if (!numeric[i])
1581 vartotal -= len[i];
1582 break;
1583 }
1584
1585 lentotal += (hdr[i] > len[i] ? hdr[i] : len[i]);
1586 printfields++;
1587 }
1588
1589 /* what we waste on space on the display is the column separators '
1590 * | ', but the edges lack the edgespace of course */
1591 graphwaste = ((printfields * 3) - 1) + 2;
1592 /* make sure we can indicate we dropped columns */
1593 if (fields != printfields)
1594 graphwaste++;
1595
1596 /* punish the column headers first until you cannot squeeze any
1597 * further */
1598 while (pagewidth > 0 && graphwaste + lentotal > pagewidth) {
1599 /* pick the column where the header is longest compared to its
1600 * content */
1601 max = -1;
1602 for (i = 0; i < printfields; i++) {
1603 if (hdr[i] > len[i]) {
1604 if (max == -1 ||
1605 hdr[max] - len[max] < hdr[i] - len[i])
1606 max = i;
1607 }
1608 }
1609 if (max == -1)
1610 break;
1611 hdr[max]--;
1612 lentotal--;
1613 }
1614
1615 /* correct the lengths if the headers are wider than the content,
1616 * since the headers are maximally squeezed to the content above, if
1617 * a header is larger than its content, it means there was space
1618 * enough. If not, the content will be squeezed below. */
1619 for (i = 0; i < printfields; i++)
1620 if (len[i] < hdr[i])
1621 len[i] = hdr[i];
1622
1623 /* worst case: lentotal = total, which means it still doesn't fit,
1624 * values will be squeezed next */
1625 while (pagewidth > 0 && graphwaste + total > pagewidth) {
1626 max = -1;
1627 for (i = 0; i < printfields; i++) {
1628 if (!numeric[i] && (max == -1 || len[i] > len[max]))
1629 max = i;
1630 }
1631
1632 /* no varsized fields that we can squeeze */
1633 if (max == -1)
1634 break;
1635 /* penalty for largest field */
1636 len[max]--;
1637 total--;
1638 /* no more squeezing possible */
1639 if (len[max] == 1)
1640 break;
1641 }
1642
1643 SQLheader(hdl, len, printfields, fields != printfields);
1644
1645 while ((rfields = fetch_row(hdl)) != 0) {
1646 if (mnstr_errnr(toConsole))
1647 continue;
1648 if (rfields != fields) {
1649 mnstr_printf(stderr_stream,
1650 "invalid tuple received from server, "
1651 "got %d columns, expected %d, ignoring\n", rfields, fields);
1652 continue;
1653 }
1654 if (silent)
1655 continue;
1656 for (i = 0; i < printfields; i++) {
1657 rest[i] = mapi_fetch_field(hdl, i);
1658 if (rest[i] == NULL)
1659 rest[i] = nullstring;
1660 else {
1661 char *p = rest[i];
1662
1663 while ((p = strchr(p, '\r')) != 0) {
1664 switch (p[1]) {
1665 case '\0':
1666 /* end of string: remove CR */
1667 *p = 0;
1668 break;
1669 case '\n':
1670 /* followed by LF: remove CR */
1671 /* note: copy including NUL */
1672 memmove(p, p + 1, strlen(p));
1673 break;
1674 default:
1675 /* replace with ' ' */
1676 *p = ' ';
1677 break;
1678 }
1679 }
1680 }
1681 }
1682
1683 if (ps > 0 && rows >= ps && fromConsole != NULL) {
1684 SQLpagemove(len, printfields, &ps, &silent);
1685 rows = 0;
1686 if (silent)
1687 continue;
1688 }
1689
1690 rows += SQLrow(len, numeric, rest, printfields, 2, 0);
1691 }
1692 if (fields)
1693 SQLseparator(len, printfields, '-');
1694 rows = mapi_get_row_count(hdl);
1695 snprintf(buf, sizeof(buf), "%" PRId64 " rows", rows);
1696 mnstr_printf(toConsole, "%" PRId64 " tuple%s", rows, rows != 1 ? "s" : "");
1697
1698 if (fields != printfields || croppedfields > 0)
1699 mnstr_printf(toConsole, " !");
1700 if (fields != printfields) {
1701 rows = fields - printfields;
1702 mnstr_printf(toConsole, "%" PRId64 " column%s dropped", rows, rows != 1 ? "s" : "");
1703 }
1704 if (fields != printfields && croppedfields > 0)
1705 mnstr_printf(toConsole, ", ");
1706 if (croppedfields > 0)
1707 mnstr_printf(toConsole, "%d field%s truncated",
1708 croppedfields, croppedfields != 1 ? "s" : "");
1709 if (fields != printfields || croppedfields > 0) {
1710 mnstr_printf(toConsole, "!");
1711 if (firstcrop) {
1712 firstcrop = false;
1713 mnstr_printf(toConsole, "\nnote: to disable dropping columns and/or truncating fields use \\w-1");
1714 }
1715 }
1716 mnstr_printf(toConsole, "\n");
1717
1718 free(len);
1719 free(hdr);
1720 free(rest);
1721 free(numeric);
1722}
1723
1724static void
1725setFormatter(const char *s)
1726{
1727 if (separator)
1728 free(separator);
1729 separator = NULL;
1730 csvheader = false;
1731#ifdef _TWO_DIGIT_EXPONENT
1732 if (formatter == TESTformatter)
1733 _set_output_format(0);
1734#endif
1735 if (strcmp(s, "sql") == 0) {
1736 formatter = TABLEformatter;
1737 } else if (strcmp(s, "csv") == 0) {
1738 formatter = CSVformatter;
1739 separator = strdup(",");
1740 } else if (strncmp(s, "csv=", 4) == 0) {
1741 formatter = CSVformatter;
1742 if (s[4] == '"') {
1743 separator = strdup(s + 5);
1744 if (separator[strlen(separator) - 1] == '"')
1745 separator[strlen(separator) - 1] = 0;
1746 } else
1747 separator = strdup(s + 4);
1748 } else if (strncmp(s, "csv+", 4) == 0) {
1749 formatter = CSVformatter;
1750 if (s[4] == '"') {
1751 separator = strdup(s + 5);
1752 if (separator[strlen(separator) - 1] == '"')
1753 separator[strlen(separator) - 1] = 0;
1754 } else
1755 separator = strdup(s + 4);
1756 csvheader = true;
1757 } else if (strcmp(s, "tab") == 0) {
1758 formatter = CSVformatter;
1759 separator = strdup("\t");
1760 } else if (strcmp(s, "raw") == 0) {
1761 formatter = RAWformatter;
1762 } else if (strcmp(s, "xml") == 0) {
1763 formatter = XMLformatter;
1764 } else if (strcmp(s, "test") == 0) {
1765#ifdef _TWO_DIGIT_EXPONENT
1766 _set_output_format(_TWO_DIGIT_EXPONENT);
1767#endif
1768 formatter = TESTformatter;
1769 } else if (strcmp(s, "trash") == 0) {
1770 formatter = TRASHformatter;
1771 } else if (strcmp(s, "rowcount") == 0) {
1772 formatter = ROWCOUNTformatter;
1773 } else if (strcmp(s, "sam") == 0) {
1774 formatter = SAMformatter;
1775 } else if (strcmp(s, "x") == 0 || strcmp(s, "expanded") == 0) {
1776 formatter = EXPANDEDformatter;
1777 } else {
1778 mnstr_printf(toConsole, "unsupported formatter\n");
1779 }
1780}
1781
1782static void
1783setWidth(void)
1784{
1785 if (!pagewidthset) {
1786#ifdef TIOCGWINSZ
1787 struct winsize ws;
1788
1789 if (ioctl(fileno(stdout), TIOCGWINSZ, &ws) == 0 && ws.ws_col > 0)
1790 pagewidth = ws.ws_col;
1791 else
1792#endif
1793 {
1794#ifdef WIN32
1795 pagewidth = 79; /* 80 columns minus 1 for the edge */
1796#else
1797 pagewidth = -1;
1798#endif
1799 }
1800 }
1801}
1802
1803#ifdef HAVE_POPEN
1804static void
1805start_pager(stream **saveFD)
1806{
1807 *saveFD = NULL;
1808
1809 if (pager) {
1810 FILE *p;
1811 struct sigaction act;
1812
1813 /* ignore SIGPIPE so that we get an error instead of signal */
1814 act.sa_handler = SIG_IGN;
1815 (void) sigemptyset(&act.sa_mask);
1816 act.sa_flags = 0;
1817 if(sigaction(SIGPIPE, &act, NULL) == -1) {
1818 fprintf(stderr, "Starting '%s' failed\n", pager);
1819 } else {
1820 p = popen(pager, "w");
1821 if (p == NULL)
1822 fprintf(stderr, "Starting '%s' failed\n", pager);
1823 else {
1824 *saveFD = toConsole;
1825 /* put | in name to indicate that file should be closed with pclose */
1826 if ((toConsole = file_wastream(p, "|pager")) == NULL) {
1827 toConsole = *saveFD;
1828 *saveFD = NULL;
1829 fprintf(stderr, "Starting '%s' failed\n", pager);
1830 }
1831#ifdef HAVE_ICONV
1832 if (encoding != NULL) {
1833 if ((toConsole = iconv_wstream(toConsole, encoding, "pager")) == NULL) {
1834 toConsole = *saveFD;
1835 *saveFD = NULL;
1836 fprintf(stderr, "Starting '%s' failed\n", pager);
1837 }
1838 }
1839#endif
1840 }
1841 }
1842 }
1843}
1844
1845static void
1846end_pager(stream *saveFD)
1847{
1848 if (saveFD) {
1849 close_stream(toConsole);
1850 toConsole = saveFD;
1851 }
1852}
1853#endif
1854
1855static int
1856format_result(Mapi mid, MapiHdl hdl, bool singleinstr)
1857{
1858 MapiMsg rc = MERROR;
1859 int64_t aff, lid;
1860 char *reply;
1861 int64_t sqloptimizer = 0;
1862 int64_t maloptimizer = 0;
1863 int64_t querytime = 0;
1864 int64_t rows = 0;
1865#ifdef HAVE_POPEN
1866 stream *saveFD;
1867
1868 start_pager(&saveFD);
1869#endif
1870
1871 setWidth();
1872
1873 timerHumanCalled = false;
1874
1875 do {
1876 // get the timings as reported by the backend
1877 sqloptimizer = mapi_get_sqloptimizertime(hdl);
1878 maloptimizer = mapi_get_maloptimizertime(hdl);
1879 querytime = mapi_get_querytime(hdl);
1880 timerHumanStop();
1881 /* handle errors first */
1882 if (mapi_result_error(hdl) != NULL) {
1883 mnstr_flush(toConsole);
1884 if (formatter == TABLEformatter) {
1885 mapi_noexplain(mid, "");
1886 } else {
1887 mapi_noexplain(mid, NULL);
1888 }
1889 mapi_explain_result(hdl, stderr);
1890 errseen = true;
1891 /* don't need to print something like '0
1892 * tuples' if we got an error */
1893 timerHuman(sqloptimizer, maloptimizer, querytime, singleinstr, false);
1894 continue;
1895 }
1896
1897 switch (mapi_get_querytype(hdl)) {
1898 case Q_BLOCK:
1899 case Q_PARSE:
1900 /* should never see these */
1901 continue;
1902 case Q_UPDATE:
1903 SQLqueryEcho(hdl);
1904 if (formatter == RAWformatter ||
1905 formatter == TESTformatter) {
1906 mnstr_printf(toConsole, "[ %" PRId64 "\t]\n", mapi_rows_affected(hdl));
1907 } else if (formatter != TRASHformatter) {
1908 aff = mapi_rows_affected(hdl);
1909 lid = mapi_get_last_id(hdl);
1910 mnstr_printf(toConsole,
1911 "%" PRId64 " affected row%s",
1912 aff,
1913 aff != 1 ? "s" : "");
1914 if (lid != -1) {
1915 mnstr_printf(toConsole,
1916 ", last generated key: "
1917 "%" PRId64,
1918 lid);
1919 }
1920 mnstr_printf(toConsole, "\n");
1921 }
1922 timerHuman(sqloptimizer, maloptimizer, querytime, singleinstr, false);
1923 continue;
1924 case Q_SCHEMA:
1925 SQLqueryEcho(hdl);
1926 if (formatter == TABLEformatter ||
1927 formatter == ROWCOUNTformatter) {
1928 mnstr_printf(toConsole, "operation successful\n");
1929 }
1930 timerHuman(sqloptimizer, maloptimizer, querytime, singleinstr, false);
1931 continue;
1932 case Q_TRANS:
1933 SQLqueryEcho(hdl);
1934 if (formatter == TABLEformatter ||
1935 formatter == ROWCOUNTformatter)
1936 mnstr_printf(toConsole, "operation successful\n");
1937 timerHuman(sqloptimizer, maloptimizer, querytime, singleinstr, false);
1938 continue;
1939 case Q_PREPARE:
1940 SQLqueryEcho(hdl);
1941 if (formatter == TABLEformatter ||
1942 formatter == ROWCOUNTformatter)
1943 mnstr_printf(toConsole,
1944 "execute prepared statement "
1945 "using: EXEC %d(...)\n",
1946 mapi_get_tableid(hdl));
1947 timerHuman(sqloptimizer, maloptimizer, querytime, singleinstr, false);
1948 break;
1949 case Q_TABLE:
1950 break;
1951 default:
1952 if ((formatter == TABLEformatter ||
1953 formatter == ROWCOUNTformatter) &&
1954 specials != DEBUGmodifier) {
1955 int i;
1956 mnstr_printf(stderr_stream,
1957 "invalid/unknown response from server, "
1958 "ignoring output\n");
1959 for (i = 0; i < 5 && (reply = fetch_line(hdl)) != 0; i++)
1960 mnstr_printf(stderr_stream, "? %s\n", reply);
1961 if (i == 5 && fetch_line(hdl) != 0) {
1962 mnstr_printf(stderr_stream,
1963 "(remaining output omitted, "
1964 "use \\fraw to examine in detail)\n");
1965 /* skip over the
1966 * unknown/invalid stuff,
1967 * otherwise mapi_next_result
1968 * call will assert in
1969 * close_result because the
1970 * logic there doesn't expect
1971 * random unread garbage
1972 * somehow */
1973 while (fetch_line(hdl) != 0)
1974 ;
1975 }
1976 continue;
1977 }
1978 }
1979
1980 /* note: specials != NOmodifier implies mode == SQL */
1981 if (specials != NOmodifier && debugMode()) {
1982 SQLdebugRendering(hdl);
1983 continue;
1984 }
1985 if (debugMode())
1986 RAWrenderer(hdl);
1987 else {
1988 SQLqueryEcho(hdl);
1989
1990 switch (formatter) {
1991 case TRASHformatter:
1992 break;
1993 case XMLformatter:
1994 XMLrenderer(hdl);
1995 break;
1996 case CSVformatter:
1997 CSVrenderer(hdl);
1998 break;
1999 case TESTformatter:
2000 TESTrenderer(hdl);
2001 break;
2002 case TABLEformatter:
2003 switch (specials) {
2004 case DEBUGmodifier:
2005 SQLdebugRendering(hdl);
2006 break;
2007 default:
2008 SQLrenderer(hdl);
2009 break;
2010 }
2011 break;
2012 case ROWCOUNTformatter:
2013 rows = mapi_get_row_count(hdl);
2014 mnstr_printf(toConsole,
2015 "%" PRId64 " tuple%s\n", rows, rows != 1 ? "s" : "");
2016 break;
2017 case SAMformatter:
2018 SAMrenderer(hdl);
2019 break;
2020 case EXPANDEDformatter:
2021 EXPANDEDrenderer(hdl);
2022 break;
2023 default:
2024 RAWrenderer(hdl);
2025 break;
2026 }
2027
2028 timerHuman(sqloptimizer, maloptimizer, querytime, singleinstr, false);
2029 }
2030 } while (!mnstr_errnr(toConsole) && (rc = mapi_next_result(hdl)) == 1);
2031 /*
2032 * in case we called timerHuman() in the loop above with "total == false",
2033 * call it again with "total == true" to get the total wall-clock time
2034 * in case "singleinstr == false".
2035 */
2036 if (timerHumanCalled)
2037 timerHuman(sqloptimizer, maloptimizer, querytime, singleinstr, true);
2038 if (mnstr_errnr(toConsole)) {
2039 mnstr_clearerr(toConsole);
2040 fprintf(stderr, "write error\n");
2041 errseen = true;
2042 }
2043#ifdef HAVE_POPEN
2044 end_pager(saveFD);
2045#endif
2046
2047 return rc;
2048}
2049
2050static int
2051doRequest(Mapi mid, const char *buf)
2052{
2053 MapiHdl hdl;
2054
2055 if (mode == SQL)
2056 SQLsetSpecial(buf);
2057
2058 hdl = mapi_query(mid, buf);
2059 if (hdl == NULL) {
2060 if (formatter == TABLEformatter) {
2061 mapi_noexplain(mid, "");
2062 } else {
2063 mapi_noexplain(mid, NULL);
2064 }
2065 mapi_explain(mid, stderr);
2066 errseen = true;
2067 return 1;
2068 }
2069
2070 if (mapi_needmore(hdl) == MMORE)
2071 return 0;
2072
2073 format_result(mid, hdl, false);
2074
2075 if (mapi_get_active(mid) == NULL)
2076 mapi_close_handle(hdl);
2077 return 0;
2078}
2079
2080#define CHECK_RESULT(mid, hdl, break_or_continue, buf, fp) \
2081 switch (mapi_error(mid)) { \
2082 case MOK: \
2083 /* everything A OK */ \
2084 break; \
2085 case MERROR: \
2086 /* some error, but try to continue */ \
2087 if (formatter == TABLEformatter) { \
2088 mapi_noexplain(mid, ""); \
2089 } else { \
2090 mapi_noexplain(mid, NULL); \
2091 } \
2092 if (hdl) { \
2093 mapi_explain_query(hdl, stderr); \
2094 mapi_close_handle(hdl); \
2095 hdl = NULL; \
2096 } else \
2097 mapi_explain(mid, stderr); \
2098 errseen = true; \
2099 break_or_continue; \
2100 case MTIMEOUT: \
2101 /* lost contact with the server */ \
2102 if (formatter == TABLEformatter) { \
2103 mapi_noexplain(mid, ""); \
2104 } else { \
2105 mapi_noexplain(mid, NULL); \
2106 } \
2107 if (hdl) { \
2108 mapi_explain_query(hdl, stderr); \
2109 mapi_close_handle(hdl); \
2110 hdl = NULL; \
2111 } else \
2112 mapi_explain(mid, stderr); \
2113 errseen = true; \
2114 timerEnd(); \
2115 if (buf) \
2116 free(buf); \
2117 if (fp) \
2118 close_stream(fp); \
2119 return 1; \
2120 }
2121
2122static bool
2123doFileBulk(Mapi mid, stream *fp)
2124{
2125 char *buf = NULL;
2126 ssize_t length;
2127 MapiHdl hdl = mapi_get_active(mid);
2128 MapiMsg rc = MOK;
2129 size_t bufsize = 0;
2130
2131 bufsize = 10240;
2132 buf = malloc(bufsize + 1);
2133 if (!buf) {
2134 fprintf(stderr, "cannot allocate memory for send buffer\n");
2135 if (fp)
2136 close_stream(fp);
2137 return true;
2138 }
2139
2140 timerStart();
2141 do {
2142 timerPause();
2143 if (fp == NULL) {
2144 if (hdl == NULL)
2145 break;
2146 length = 0;
2147 buf[0] = 0;
2148 } else if ((length = mnstr_read(fp, buf, 1, bufsize)) <= 0) {
2149 /* end of file or error */
2150 if (hdl == NULL)
2151 break; /* nothing more to do */
2152 buf[0] = 0;
2153 length = 0; /* handle error like EOF */
2154 } else {
2155 buf[length] = 0;
2156 if (strlen(buf) < (size_t) length) {
2157 fprintf(stderr, "NULL byte in input\n");
2158 errseen = true;
2159 break;
2160 }
2161 }
2162 timerResume();
2163 if (hdl == NULL) {
2164 hdl = mapi_query_prep(mid);
2165 CHECK_RESULT(mid, hdl, continue, buf, fp);
2166 }
2167
2168 assert(hdl != NULL);
2169 mapi_query_part(hdl, buf, (size_t) length);
2170 CHECK_RESULT(mid, hdl, continue, buf, fp);
2171
2172 /* if not at EOF, make sure there is a newline in the
2173 * buffer */
2174 if (length > 0 && strchr(buf, '\n') == NULL)
2175 continue;
2176
2177 assert(hdl != NULL);
2178 /* If the server wants more but we're at the end of
2179 * file (length == 0), notify the server that we
2180 * don't have anything more. If the server still
2181 * wants more (shouldn't happen according to the
2182 * protocol) we break out of the loop (via the
2183 * continue). The assertion at the end will then go
2184 * off. */
2185 if (mapi_query_done(hdl) == MMORE &&
2186 (length > 0 || mapi_query_done(hdl) == MMORE))
2187 continue; /* get more data */
2188
2189 CHECK_RESULT(mid, hdl, continue, buf, fp);
2190
2191 rc = format_result(mid, hdl, false);
2192
2193 if (rc == MMORE && (length > 0 || mapi_query_done(hdl) != MOK))
2194 continue; /* get more data */
2195
2196 CHECK_RESULT(mid, hdl, continue, buf, fp);
2197
2198 mapi_close_handle(hdl);
2199 hdl = NULL;
2200
2201 } while (length > 0);
2202 /* reached on end of file */
2203 if (hdl)
2204 mapi_close_handle(hdl);
2205 timerEnd();
2206
2207 free(buf);
2208 mnstr_flush(toConsole);
2209 if (fp)
2210 close_stream(fp);
2211 return errseen;
2212}
2213
2214/* The options available for controlling input and rendering depends
2215 * on the language mode. */
2216
2217static void
2218showCommands(void)
2219{
2220 /* shared control options */
2221 mnstr_printf(toConsole, "\\? - show this message\n");
2222 if (mode == MAL)
2223 mnstr_printf(toConsole, "?pat - MAL function help. pat=[modnme[.fcnnme][(][)]] wildcard *\n");
2224 mnstr_printf(toConsole, "\\<file - read input from file\n");
2225 mnstr_printf(toConsole, "\\>file - save response in file, or stdout if no file is given\n");
2226#ifdef HAVE_POPEN
2227 mnstr_printf(toConsole, "\\|cmd - pipe result to process, or stop when no command is given\n");
2228#endif
2229#ifdef HAVE_LIBREADLINE
2230 mnstr_printf(toConsole, "\\history - show the readline history\n");
2231#endif
2232 if (mode == SQL) {
2233 mnstr_printf(toConsole, "\\help - synopsis of the SQL syntax\n");
2234 mnstr_printf(toConsole, "\\D table - dumps the table, or the complete database if none given.\n");
2235 mnstr_printf(toConsole, "\\d[Stvsfn]+ [obj] - list database objects, or describe if obj given\n");
2236 mnstr_printf(toConsole, "\\A - enable auto commit\n");
2237 mnstr_printf(toConsole, "\\a - disable auto commit\n");
2238 }
2239 mnstr_printf(toConsole, "\\e - echo the query in sql formatting mode\n");
2240 mnstr_printf(toConsole, "\\t - set the timer {none,clock,performance} (none is default)\n");
2241 mnstr_printf(toConsole, "\\f - format using renderer {csv,tab,raw,sql,xml,trash,rowcount,expanded,sam}\n");
2242 mnstr_printf(toConsole, "\\w# - set maximal page width (-1=unlimited, 0=terminal width, >0=limit to num)\n");
2243 mnstr_printf(toConsole, "\\r# - set maximum rows per page (-1=raw)\n");
2244 mnstr_printf(toConsole, "\\L file - save client-server interaction\n");
2245 mnstr_printf(toConsole, "\\X - trace mclient code\n");
2246 mnstr_printf(toConsole, "\\q - terminate session and quit mclient\n");
2247}
2248
2249#define MD_TABLE 1
2250#define MD_VIEW 2
2251#define MD_SEQ 4
2252#define MD_FUNC 8
2253#define MD_SCHEMA 16
2254
2255#define READBLOCK 8192
2256
2257#ifdef HAVE_LIBREADLINE
2258struct myread_t {
2259 stream *s;
2260 const char *prompt;
2261 char *buf;
2262 size_t read;
2263 size_t len;
2264};
2265
2266static ssize_t
2267myread(void *restrict private, void *restrict buf, size_t elmsize, size_t cnt)
2268{
2269 struct myread_t *p = private;
2270 size_t size = elmsize * cnt;
2271 size_t cpsize = size;
2272
2273 assert(elmsize == 1);
2274 if (size == 0)
2275 return cnt;
2276 if (p->buf == NULL) {
2277 rl_completion_func_t *func = NULL;
2278
2279 if (strcmp(p->prompt, "more>") == 0)
2280 func = suspend_completion();
2281 p->buf = readline(p->prompt);
2282 if (func)
2283 continue_completion(func);
2284 if (p->buf == NULL)
2285 return 0;
2286 p->len = strlen(p->buf);
2287 p->read = 0;
2288 if (p->len > 1)
2289 save_line(p->buf);
2290 }
2291 if (p->read < p->len) {
2292 if (p->len - p->read < size)
2293 cpsize = p->len - p->read;
2294 memcpy(buf, p->buf + p->read, cpsize);
2295 p->read += cpsize;
2296 } else {
2297 cpsize = 0;
2298 }
2299 if (p->read == p->len && cpsize < size) {
2300 ((char *) buf)[cpsize++] = '\n';
2301 free(p->buf);
2302 p->buf = NULL;
2303 }
2304 return cpsize / elmsize;
2305}
2306
2307static void
2308mydestroy(void *private)
2309{
2310 struct myread_t *p = private;
2311
2312 if (p->buf)
2313 free(p->buf);
2314}
2315#endif
2316
2317static bool
2318doFile(Mapi mid, stream *fp, bool useinserts, bool interactive, int save_history)
2319{
2320 char *line = NULL;
2321 char *buf = NULL;
2322 size_t length;
2323 size_t bufsiz = 0;
2324 MapiHdl hdl;
2325 MapiMsg rc = MOK;
2326 int lineno = 1;
2327 char *prompt = NULL;
2328 int prepno = 0;
2329#ifdef HAVE_LIBREADLINE
2330 struct myread_t rl;
2331#endif
2332 int fd;
2333
2334 (void) save_history; /* not used if no readline */
2335 if ((fd = getFileNo(fp)) >= 0 && isatty(fd)
2336#ifdef WIN32 /* isatty may not give expected result */
2337 && formatter != TESTformatter
2338#endif
2339 ) {
2340 interactive = true;
2341 setPrompt();
2342 prompt = promptbuf;
2343 fromConsole = fp;
2344#ifdef HAVE_LIBREADLINE
2345 init_readline(mid, language, save_history);
2346 rl.s = fp;
2347 rl.buf = NULL;
2348 if ((fp = callback_stream(&rl, myread, NULL, mydestroy, mnstr_name(fp))) == NULL) {
2349 fprintf(stderr,"Malloc for doFile failed");
2350 exit(2);
2351 }
2352#endif
2353 }
2354#ifdef HAVE_ICONV
2355 if (encoding) {
2356 if ((fp = iconv_rstream(fp, encoding, mnstr_name(fp))) == NULL) {
2357 fprintf(stderr,"Malloc failure");
2358 exit(2);
2359 }
2360 }
2361#endif
2362
2363 if (!interactive && !echoquery)
2364 return doFileBulk(mid, fp);
2365
2366 hdl = mapi_get_active(mid);
2367
2368 bufsiz = READBLOCK;
2369 buf = malloc(bufsiz);
2370 if (buf == NULL) {
2371 fprintf(stderr,"Malloc for doFile failed");
2372 exit(2);
2373 }
2374
2375 do {
2376 bool seen_null_byte = false;
2377
2378 if (prompt) {
2379 char *p = hdl ? "more>" : prompt;
2380 /* clear errors when interactive */
2381 errseen = false;
2382#ifdef HAVE_LIBREADLINE
2383 rl.prompt = p;
2384#else
2385 mnstr_write(toConsole, p, 1, strlen(p));
2386#endif
2387 }
2388 mnstr_flush(toConsole);
2389 timerPause();
2390 /* read a line */
2391 length = 0;
2392 for (;;) {
2393 ssize_t l;
2394 char *newbuf;
2395 l = mnstr_readline(fp, buf + length, bufsiz - length);
2396 if (l <= 0)
2397 break;
2398 if (!seen_null_byte && strlen(buf + length) < (size_t) l) {
2399 fprintf(stderr, "NULL byte in input on line %d of input\n", lineno);
2400 seen_null_byte = true;
2401 errseen = true;
2402 if (hdl) {
2403 mapi_close_handle(hdl);
2404 hdl = NULL;
2405 }
2406 }
2407 length += l;
2408 if (buf[length - 1] == '\n')
2409 break;
2410 newbuf = realloc(buf, bufsiz += READBLOCK);
2411 if (newbuf) {
2412 buf = newbuf;
2413 } else {
2414 fprintf(stderr,"Malloc failure");
2415 length = 0;
2416 errseen = true;
2417 if (hdl) {
2418 mapi_close_handle(hdl);
2419 hdl = NULL;
2420 }
2421 break;
2422 }
2423 }
2424 line = buf;
2425 lineno++;
2426 if (seen_null_byte)
2427 continue;
2428 if (length == 0) {
2429 /* end of file */
2430 if (hdl == NULL) {
2431 /* nothing more to do */
2432 goto bailout;
2433 }
2434
2435 /* hdl != NULL, we should finish the current query */
2436 }
2437 if (hdl == NULL && length > 0 && interactive) {
2438 /* test for special commands */
2439 if (mode != MAL)
2440 while (length > 0 &&
2441 (*line == '\f' ||
2442 *line == '\n' ||
2443 *line == ' ')) {
2444 line++;
2445 length--;
2446 }
2447 /* in the switch, use continue if the line was
2448 * processed, use break to send to server */
2449 switch (*line) {
2450 case '\n':
2451 case '\0':
2452 break;
2453 case 'e':
2454 /* a bit of a hack for prepare/exec
2455 * tests: replace "exec **" with the
2456 * ID of the last prepared
2457 * statement */
2458 if (mode == SQL &&
2459 formatter == TESTformatter &&
2460 strncmp(line, "exec **", 7) == 0) {
2461 line[5] = prepno < 10 ? ' ' : prepno / 10 + '0';
2462 line[6] = prepno % 10 + '0';
2463 }
2464 if (strcmp(line, "exit\n") == 0) {
2465 goto bailout;
2466 }
2467 break;
2468 case 'q':
2469 if (strcmp(line, "quit\n") == 0) {
2470 goto bailout;
2471 }
2472 break;
2473 case '\\':
2474 switch (line[1]) {
2475 case 'q':
2476 goto bailout;
2477 case 'X':
2478 /* toggle interaction trace */
2479 mapi_trace(mid, !mapi_get_trace(mid));
2480 continue;
2481 case 'A':
2482 if (mode != SQL)
2483 break;
2484 mapi_setAutocommit(mid, true);
2485 continue;
2486 case 'a':
2487 if (mode != SQL)
2488 break;
2489 mapi_setAutocommit(mid, false);
2490 continue;
2491 case 'w':
2492 pagewidth = atoi(line + 2);
2493 pagewidthset = pagewidth != 0;
2494 continue;
2495 case 'r':
2496 rowsperpage = atoi(line + 2);
2497 continue;
2498 case 'd': {
2499 bool hasWildcard = false;
2500 bool hasSchema = false;
2501 bool wantsSystem = false;
2502 unsigned int x = 0;
2503 char *p, *q;
2504 bool escaped = false;
2505 if (mode != SQL)
2506 break;
2507 while (my_isspace(line[length - 1]))
2508 line[--length] = 0;
2509 for (line += 2;
2510 *line && !my_isspace(*line);
2511 line++) {
2512 switch (*line) {
2513 case 't':
2514 x |= MD_TABLE;
2515 break;
2516 case 'v':
2517 x |= MD_VIEW;
2518 break;
2519 case 's':
2520 x |= MD_SEQ;
2521 break;
2522 case 'f':
2523 x |= MD_FUNC;
2524 break;
2525 case 'n':
2526 x |= MD_SCHEMA;
2527 break;
2528 case 'S':
2529 wantsSystem = true;
2530 break;
2531 default:
2532 fprintf(stderr, "unknown sub-command for \\d: %c\n", *line);
2533 length = 0;
2534 line[1] = '\0';
2535 break;
2536 }
2537 }
2538 if (length == 0)
2539 continue;
2540 if (x == 0) /* default to tables and views */
2541 x = MD_TABLE | MD_VIEW;
2542 for ( ; *line && my_isspace(*line); line++)
2543 ;
2544
2545 /* lowercase the object, except for quoted parts */
2546 q = line;
2547 for (p = line; *p != '\0'; p++) {
2548 if (*p == '"') {
2549 if (escaped) {
2550 if (*(p + 1) == '"') {
2551 /* SQL escape */
2552 *q++ = *p++;
2553 } else {
2554 escaped = false;
2555 }
2556 } else {
2557 escaped = true;
2558 }
2559 } else {
2560 if (!escaped) {
2561 *q++ = tolower((int) *p);
2562 if (*p == '*') {
2563 *p = '%';
2564 hasWildcard = true;
2565 } else if (*p == '?') {
2566 *p = '_';
2567 hasWildcard = true;
2568 } else if (*p == '.') {
2569 hasSchema = true;
2570 }
2571 } else {
2572 *q++ = *p;
2573 }
2574 }
2575 }
2576 *q = '\0';
2577 if (escaped) {
2578 fprintf(stderr, "unexpected end of string while "
2579 "looking for matching \"\n");
2580 continue;
2581 }
2582
2583 if (*line && !hasWildcard) {
2584#ifdef HAVE_POPEN
2585 stream *saveFD;
2586
2587 start_pager(&saveFD);
2588#endif
2589 if (x & MD_TABLE || x & MD_VIEW)
2590 describe_table(mid, NULL, line, toConsole, 1, false);
2591 if (x & MD_SEQ)
2592 describe_sequence(mid, NULL, line, toConsole);
2593 if (x & MD_FUNC)
2594 dump_functions(mid, toConsole, 0, NULL, line, NULL);
2595 if (x & MD_SCHEMA)
2596 describe_schema(mid, line, toConsole);
2597#ifdef HAVE_POPEN
2598 end_pager(saveFD);
2599#endif
2600 } else {
2601 /* get all object names in current schema */
2602 char *with_clause =
2603 ", describe_all_objects AS (\n"
2604 " SELECT s.name AS sname,\n"
2605 " t.name,\n"
2606 " s.name || '.' || t.name AS fullname,\n"
2607 " CAST(CASE t.type\n"
2608 " WHEN 1 THEN 2\n" /* ntype for views */
2609 " ELSE 1\n" /* ntype for tables */
2610 " END AS SMALLINT) AS ntype,\n"
2611 " (CASE WHEN t.system THEN 'SYSTEM ' ELSE '' END) || tt.table_type_name AS type,\n"
2612 " t.system,\n"
2613 " c.remark AS remark\n"
2614 " FROM sys._tables t\n"
2615 " LEFT OUTER JOIN comments c ON t.id = c.id\n"
2616 " LEFT OUTER JOIN sys.schemas s ON t.schema_id = s.id\n"
2617 " LEFT OUTER JOIN sys.table_types tt ON t.type = tt.table_type_id\n"
2618 " UNION ALL\n"
2619 " SELECT s.name AS sname,\n"
2620 " sq.name,\n"
2621 " s.name || '.' || sq.name AS fullname,\n"
2622 " CAST(4 AS SMALLINT) AS ntype,\n"
2623 " 'SEQUENCE' AS type,\n"
2624 " false AS system,\n"
2625 " c.remark AS remark\n"
2626 " FROM sys.sequences sq\n"
2627 " LEFT OUTER JOIN comments c ON sq.id = c.id\n"
2628 " LEFT OUTER JOIN sys.schemas s ON sq.schema_id = s.id\n"
2629 " UNION ALL\n"
2630 " SELECT DISTINCT s.name AS sname,\n" /* DISTINCT is needed to filter out duplicate overloaded function/procedure names */
2631 " f.name,\n"
2632 " s.name || '.' || f.name AS fullname,\n"
2633 " CAST(8 AS SMALLINT) AS ntype,\n"
2634 " (CASE WHEN sf.function_id IS NOT NULL THEN 'SYSTEM ' ELSE '' END) || function_type_keyword AS type,\n"
2635 " sf.function_id IS NOT NULL AS system,\n"
2636 " c.remark AS remark\n"
2637 " FROM sys.functions f\n"
2638 " LEFT OUTER JOIN comments c ON f.id = c.id\n"
2639 " LEFT OUTER JOIN function_types ft ON f.type = ft.function_type_id\n"
2640 " LEFT OUTER JOIN sys.schemas s ON f.schema_id = s.id\n"
2641 " LEFT OUTER JOIN sys.systemfunctions sf ON f.id = sf.function_id\n"
2642 " UNION ALL\n"
2643 " SELECT NULL AS sname,\n"
2644 " s.name,\n"
2645 " s.name AS fullname,\n"
2646 " CAST(16 AS SMALLINT) AS ntype,\n"
2647 " (CASE WHEN s.system THEN 'SYSTEM SCHEMA' ELSE 'SCHEMA' END) AS type,\n"
2648 " s.system,\n"
2649 " c.remark AS remark\n"
2650 " FROM sys.schemas s\n"
2651 " LEFT OUTER JOIN comments c ON s.id = c.id\n"
2652 " ORDER BY system, name, sname, ntype)\n"
2653 ;
2654 const char *comments_clause = get_comments_clause(mid);
2655 size_t len = strlen(comments_clause) + strlen(with_clause) + 400 + strlen(line);
2656 char *query = malloc(len);
2657 char *q = query, *endq = query + len;
2658
2659 if (query == NULL) {
2660 fprintf(stderr, "memory allocation failure\n");
2661 continue;
2662 }
2663
2664 /*
2665 * | LINE | SCHEMA FILTER | NAME FILTER |
2666 * |-----------------+---------------+-------------------------------|
2667 * | "" | yes | - |
2668 * | "my_table" | yes | name LIKE 'my_table' |
2669 * | "my*" | yes | name LIKE 'my%' |
2670 * | "data.my_table" | no | fullname LIKE 'data.my_table' |
2671 * | "data.my*" | no | fullname LIKE 'data.my%' |
2672 * | "*a.my*" | no | fullname LIKE '%a.my%' |
2673 */
2674 q += snprintf(q, endq - q, "%s%s", comments_clause, with_clause);
2675 q += snprintf(q, endq - q, " SELECT type, fullname, remark FROM describe_all_objects WHERE (ntype & %u) > 0", x);
2676 if (!wantsSystem) {
2677 q += snprintf(q, endq - q, " AND NOT system");
2678 }
2679 if (!hasSchema) {
2680 q += snprintf(q, endq - q, " AND (sname IS NULL OR sname = current_schema)");
2681 }
2682 if (*line) {
2683 q += snprintf(q, endq - q, " AND (%s LIKE '%s')", (hasSchema ? "fullname" : "name"), line);
2684 }
2685 q += snprintf(q, endq - q, " ORDER BY fullname, type, remark");
2686
2687 hdl = mapi_query(mid, query);
2688 free(query);
2689 CHECK_RESULT(mid, hdl, continue, buf, fp);
2690 while (fetch_row(hdl) == 3) {
2691 char *type = mapi_fetch_field(hdl, 0);
2692 char *name = mapi_fetch_field(hdl, 1);
2693 char *remark = mapi_fetch_field(hdl, 2);
2694 int type_width = mapi_get_len(hdl, 0);
2695 int name_width = mapi_get_len(hdl, 1);
2696 mnstr_printf(toConsole,
2697 "%-*s %-*s",
2698 type_width, type,
2699 name_width * (remark != NULL), name);
2700 if (remark) {
2701 char *c;
2702 mnstr_printf(toConsole, " '");
2703 for (c = remark; *c; c++) {
2704 switch (*c) {
2705 case '\'':
2706 mnstr_printf(toConsole, "''");
2707 break;
2708 default:
2709 mnstr_writeChr(toConsole, *c);
2710 }
2711 }
2712 mnstr_printf(toConsole, "'");
2713 }
2714 mnstr_printf(toConsole, "\n");
2715
2716 }
2717 mapi_close_handle(hdl);
2718 hdl = NULL;
2719 }
2720 continue;
2721 }
2722 case 'D':{
2723#ifdef HAVE_POPEN
2724 stream *saveFD;
2725#endif
2726
2727 if (mode != SQL)
2728 break;
2729 while (my_isspace(line[length - 1]))
2730 line[--length] = 0;
2731 if (line[2] && !my_isspace(line[2])) {
2732 fprintf(stderr, "space required after \\D\n");
2733 continue;
2734 }
2735 for (line += 2; *line && my_isspace(*line); line++)
2736 ;
2737#ifdef HAVE_POPEN
2738 start_pager(&saveFD);
2739#endif
2740 if (*line) {
2741 mnstr_printf(toConsole, "START TRANSACTION;\n");
2742 dump_table(mid, NULL, line, toConsole, false, true, useinserts, false);
2743 mnstr_printf(toConsole, "COMMIT;\n");
2744 } else
2745 dump_database(mid, toConsole, 0, useinserts);
2746#ifdef HAVE_POPEN
2747 end_pager(saveFD);
2748#endif
2749 continue;
2750 }
2751 case '<': {
2752 stream *s;
2753 /* read commands from file */
2754 while (my_isspace(line[length - 1]))
2755 line[--length] = 0;
2756 for (line += 2; *line && my_isspace(*line); line++)
2757 ;
2758 /* use open_rastream to
2759 * convert filename from UTF-8
2760 * to locale */
2761 if ((s = open_rastream(line)) == NULL ||
2762 mnstr_errnr(s)) {
2763 if (s)
2764 close_stream(s);
2765 fprintf(stderr, "%s: cannot open\n", line);
2766 } else
2767 doFile(mid, s, 0, 0, 0);
2768 continue;
2769 }
2770 case '>':
2771 /* redirect output to file */
2772 while (my_isspace(line[length - 1]))
2773 line[--length] = 0;
2774 for (line += 2; *line && my_isspace(*line); line++)
2775 ;
2776 if (toConsole != stdout_stream &&
2777 toConsole != stderr_stream) {
2778 close_stream(toConsole);
2779 }
2780 if (*line == 0 ||
2781 strcmp(line, "stdout") == 0)
2782 toConsole = stdout_stream;
2783 else if (strcmp(line, "stderr") == 0)
2784 toConsole = stderr_stream;
2785 else if ((toConsole = open_wastream(line)) == NULL ||
2786 mnstr_errnr(toConsole)) {
2787 if (toConsole != NULL) {
2788 close_stream(toConsole);
2789 }
2790 toConsole = stdout_stream;
2791 fprintf(stderr, "Cannot open %s\n", line);
2792 }
2793 continue;
2794 case 'L':
2795 free(logfile);
2796 logfile = NULL;
2797 while (my_isspace(line[length - 1]))
2798 line[--length] = 0;
2799 for (line += 2; *line && my_isspace(*line); line++)
2800 ;
2801 if (*line == 0) {
2802 /* turn of logging */
2803 mapi_log(mid, NULL);
2804 } else {
2805 logfile = strdup(line);
2806 mapi_log(mid, logfile);
2807 }
2808 continue;
2809 case '?':
2810 showCommands();
2811 continue;
2812#ifdef HAVE_POPEN
2813 case '|':
2814 if (pager)
2815 free(pager);
2816 pager = NULL;
2817 setWidth(); /* reset to system default */
2818
2819 while (my_isspace(line[length - 1]))
2820 line[--length] = 0;
2821 for (line += 2; *line && my_isspace(*line); line++)
2822 ;
2823 if (*line == 0)
2824 continue;
2825 pager = strdup(line);
2826 continue;
2827#endif
2828 case 'h':
2829 {
2830#ifdef HAVE_LIBREADLINE
2831 int h;
2832 char *nl;
2833
2834 if (strcmp(line,"\\history\n") == 0) {
2835 for (h = 0; h < history_length; h++) {
2836 nl = history_get(h) ? history_get(h)->line : 0;
2837 if (nl)
2838 mnstr_printf(toConsole, "%d %s\n", h, nl);
2839 }
2840 } else
2841#endif
2842 {
2843 setWidth();
2844 sql_help(line, toConsole, pagewidth <= 0 ? DEFWIDTH : pagewidth);
2845 }
2846 continue;
2847 }
2848#if 0 /* for later */
2849#ifdef HAVE_LIBREADLINE
2850 case '!':
2851 {
2852 char *nl;
2853
2854 nl = strchr(line, '\n');
2855 if (nl)
2856 *nl = 0;
2857 if (history_expand(line + 2, &nl)) {
2858 mnstr_printf(toConsole, "%s\n", nl);
2859 }
2860 mnstr_printf(toConsole, "Expansion needs work\n");
2861 continue;
2862 }
2863#endif
2864#endif /* 0 */
2865 case 'e':
2866 echoquery = true;
2867 continue;
2868 case 'f':
2869 while (my_isspace(line[length - 1]))
2870 line[--length] = 0;
2871 for (line += 2; *line && my_isspace(*line); line++)
2872 ;
2873 if (*line == 0) {
2874 mnstr_printf(toConsole, "Current formatter: ");
2875 switch (formatter) {
2876 case RAWformatter:
2877 mnstr_printf(toConsole, "raw\n");
2878 break;
2879 case TABLEformatter:
2880 mnstr_printf(toConsole, "sql\n");
2881 break;
2882 case CSVformatter:
2883 mnstr_printf(toConsole, "%s\n", separator[0] == '\t' ? "tab" : "csv");
2884 break;
2885 case TRASHformatter:
2886 mnstr_printf(toConsole, "trash\n");
2887 break;
2888 case ROWCOUNTformatter:
2889 mnstr_printf(toConsole, "rowcount\n");
2890 break;
2891 case XMLformatter:
2892 mnstr_printf(toConsole, "xml\n");
2893 break;
2894 case EXPANDEDformatter:
2895 mnstr_printf(toConsole, "expanded\n");
2896 break;
2897 case SAMformatter:
2898 mnstr_printf(toConsole, "sam\n");
2899 break;
2900 default:
2901 mnstr_printf(toConsole, "none\n");
2902 break;
2903 }
2904 } else
2905 setFormatter(line);
2906 continue;
2907 case 't':
2908 while (my_isspace(line[length - 1]))
2909 line[--length] = 0;
2910 for (line += 2; *line && my_isspace(*line); line++)
2911 ;
2912 if (*line == 0) {
2913 mnstr_printf(toConsole, "Current time formatter: ");
2914 if (timermode == T_NONE)
2915 mnstr_printf(toConsole,"none\n");
2916 if (timermode == T_CLOCK)
2917 mnstr_printf(toConsole,"clock\n");
2918 if (timermode == T_PERF)
2919 mnstr_printf(toConsole,"performance\n");
2920 } else if (strcmp(line,"none") == 0) {
2921 timermode = T_NONE;
2922 } else if (strcmp(line,"clock") == 0) {
2923 timermode = T_CLOCK;
2924 } else if (strncmp(line,"perf",4) == 0 || strcmp(line,"performance") == 0) {
2925 timermode = T_PERF;
2926 } else if (*line != '\0') {
2927 fprintf(stderr, "warning: invalid argument to -t: %s\n",
2928 line);
2929 }
2930 continue;
2931 default:
2932 showCommands();
2933 continue;
2934 }
2935 }
2936 }
2937
2938 if (hdl == NULL) {
2939 timerStart();
2940 hdl = mapi_query_prep(mid);
2941 CHECK_RESULT(mid, hdl, continue, buf, fp);
2942 } else
2943 timerResume();
2944
2945 assert(hdl != NULL);
2946
2947 if (length > 0) {
2948 SQLsetSpecial(line);
2949 mapi_query_part(hdl, line, length);
2950 CHECK_RESULT(mid, hdl, continue, buf, fp);
2951 }
2952
2953 /* If the server wants more but we're at the
2954 * end of file (line == NULL), notify the
2955 * server that we don't have anything more.
2956 * If the server still wants more (shouldn't
2957 * happen according to the protocol) we break
2958 * out of the loop (via the continue). The
2959 * assertion at the end will then go off. */
2960 if (mapi_query_done(hdl) == MMORE) {
2961 if (line != NULL) {
2962 continue; /* get more data */
2963 } else if (mapi_query_done(hdl) == MMORE) {
2964 hdl = NULL;
2965 continue; /* done */
2966 }
2967 }
2968 CHECK_RESULT(mid, hdl, continue, buf, fp);
2969
2970 if (mapi_get_querytype(hdl) == Q_PREPARE) {
2971 prepno = mapi_get_tableid(hdl);
2972 assert(prepno < 100);
2973 }
2974
2975 rc = format_result(mid, hdl, interactive || echoquery);
2976
2977 if (rc == MMORE && (line != NULL || mapi_query_done(hdl) != MOK))
2978 continue; /* get more data */
2979
2980 CHECK_RESULT(mid, hdl, continue, buf, fp);
2981
2982 timerEnd();
2983 mapi_close_handle(hdl);
2984 hdl = NULL;
2985 } while (line != NULL);
2986 /* reached on end of file */
2987 assert(hdl == NULL);
2988 bailout:
2989 free(buf);
2990#ifdef HAVE_LIBREADLINE
2991 if (prompt)
2992 deinit_readline();
2993#endif
2994 close_stream(fp);
2995 return errseen;
2996}
2997
2998static void
2999set_timezone(Mapi mid)
3000{
3001 char buf[128];
3002 int tzone;
3003 MapiHdl hdl;
3004
3005 /* figure out our current timezone */
3006#if defined HAVE_GETDYNAMICTIMEZONEINFORMATION
3007 DYNAMIC_TIME_ZONE_INFORMATION tzinf;
3008
3009 /* documentation says: UTC = localtime + Bias (in minutes),
3010 * but experimentation during DST period says, UTC = localtime
3011 * + Bias + DaylightBias, and presumably during non DST
3012 * period, UTC = localtime + Bias */
3013 switch (GetDynamicTimeZoneInformation(&tzinf)) {
3014 case TIME_ZONE_ID_STANDARD:
3015 case TIME_ZONE_ID_UNKNOWN:
3016 tzone = (int) tzinf.Bias * 60;
3017 break;
3018 case TIME_ZONE_ID_DAYLIGHT:
3019 tzone = (int) (tzinf.Bias + tzinf.DaylightBias) * 60;
3020 break;
3021 default:
3022 /* call failed, we don't know the time zone */
3023 tzone = 0;
3024 break;
3025 }
3026#elif defined HAVE_STRUCT_TM_TM_ZONE
3027 time_t t;
3028 struct tm *tmp;
3029
3030 t = time(NULL);
3031 tmp = localtime(&t);
3032 tzone = (int) -tmp->tm_gmtoff;
3033#else
3034 time_t t, lt, gt;
3035 struct tm *tmp;
3036
3037 t = time(NULL);
3038 tmp = gmtime(&t);
3039 gt = mktime(tmp);
3040 tmp = localtime(&t);
3041 tmp->tm_isdst=0; /* We need the difference without dst */
3042 lt = mktime(tmp);
3043 assert((int64_t) gt - (int64_t) lt >= (int64_t) INT_MIN && (int64_t) gt - (int64_t) lt <= (int64_t) INT_MAX);
3044 tzone = (int) (gt - lt);
3045#endif
3046 if (tzone < 0)
3047 snprintf(buf, sizeof(buf),
3048 "SET TIME ZONE INTERVAL '+%02d:%02d' HOUR TO MINUTE",
3049 -tzone / 3600, (-tzone % 3600) / 60);
3050 else
3051 snprintf(buf, sizeof(buf),
3052 "SET TIME ZONE INTERVAL '-%02d:%02d' HOUR TO MINUTE",
3053 tzone / 3600, (tzone % 3600) / 60);
3054 if ((hdl = mapi_query(mid, buf)) == NULL) {
3055 if (formatter == TABLEformatter) {
3056 mapi_noexplain(mid, "");
3057 } else {
3058 mapi_noexplain(mid, NULL);
3059 }
3060 mapi_explain(mid, stderr);
3061 errseen = true;
3062 return;
3063 }
3064 mapi_close_handle(hdl);
3065}
3066
3067struct privdata {
3068 stream *f;
3069 char *buf;
3070};
3071
3072#define READSIZE (1 << 16)
3073//#define READSIZE (1 << 20)
3074
3075static char alpha[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
3076 "abcdefghijklmnopqrstuvwxyz";
3077
3078static char *
3079getfile(void *data, const char *filename, bool binary,
3080 uint64_t offset, size_t *size)
3081{
3082 stream *f;
3083 char *buf;
3084 struct privdata *priv = data;
3085 ssize_t s;
3086
3087 if (size)
3088 *size = 0; /* most returns require this */
3089 if (priv->buf == NULL) {
3090 priv->buf = malloc(READSIZE);
3091 if (priv->buf == NULL)
3092 return "allocation failed in client";
3093 }
3094 buf = priv->buf;
3095 if (filename != NULL) {
3096 if (binary) {
3097 f = open_rstream(filename);
3098 assert(offset <= 1);
3099 offset = 0;
3100 } else {
3101 f = open_rastream(filename);
3102 if (f == NULL) {
3103 size_t x;
3104 /* simplistic check for URL
3105 * (schema://...) */
3106 if ((x = strspn(filename, alpha)) > 0
3107 && filename[x] == ':'
3108 && filename[x+1] == '/'
3109 && filename[x+2] == '/') {
3110 if (allow_remote)
3111 f = open_urlstream(filename);
3112 else
3113 return "client refuses to retrieve remote content";
3114 }
3115 }
3116#ifdef HAVE_ICONV
3117 else if (encoding) {
3118 stream *tmpf = f;
3119 f = iconv_rstream(f, encoding, mnstr_name(f));
3120 if (f == NULL)
3121 close_stream(tmpf);
3122 }
3123#endif
3124 }
3125 if (f == NULL)
3126 return "cannot open file";
3127 while (offset > 1) {
3128 s = mnstr_readline(f, buf, READSIZE);
3129 if (s < 0) {
3130 close_stream(f);
3131 return "error reading file";
3132 }
3133 if (s == 0) {
3134 /* reached EOF withing offset lines */
3135 close_stream(f);
3136 return NULL;
3137 }
3138 if (buf[s - 1] == '\n')
3139 offset--;
3140 }
3141 priv->f = f;
3142 } else {
3143 f = priv->f;
3144 if (size == NULL) {
3145 /* done reading before reaching EOF */
3146 close_stream(f);
3147 priv->f = NULL;
3148 return NULL;
3149 }
3150 }
3151 s = mnstr_read(f, buf, 1, READSIZE);
3152 if (s <= 0) {
3153 close_stream(f);
3154 priv->f = NULL;
3155 return s < 0 ? "error reading file" : NULL;
3156 }
3157 *size = (size_t) s;
3158 return buf;
3159}
3160
3161static char *
3162putfile(void *data, const char *filename, const void *buf, size_t bufsize)
3163{
3164 struct privdata *priv = data;
3165
3166 if (filename != NULL) {
3167 if ((priv->f = open_wastream(filename)) == NULL)
3168 return "cannot open file";
3169#ifdef HAVE_ICONV
3170 if (encoding) {
3171 stream *f = priv->f;
3172 priv->f = iconv_wstream(f, encoding, mnstr_name(f));
3173 if (priv->f == NULL) {
3174 close_stream(f);
3175 return "cannot open file";
3176 }
3177 }
3178#endif
3179 if (buf == NULL || bufsize == 0)
3180 return NULL; /* successfully opened file */
3181 } else if (buf == NULL) {
3182 /* done writing */
3183 int flush = mnstr_flush(priv->f);
3184 close_stream(priv->f);
3185 priv->f = NULL;
3186 return flush < 0 ? "error writing output" : NULL;
3187 }
3188 if (mnstr_write(priv->f, buf, 1, bufsize) < (ssize_t) bufsize) {
3189 close_stream(priv->f);
3190 priv->f = NULL;
3191 return "error writing output";
3192 }
3193 return NULL; /* success */
3194}
3195
3196static _Noreturn void usage(const char *prog, int xit);
3197
3198static void
3199usage(const char *prog, int xit)
3200{
3201 fprintf(stderr, "Usage: %s [ options ] [ file or database [ file ... ] ]\n", prog);
3202 fprintf(stderr, "\nOptions are:\n");
3203#ifdef HAVE_SYS_UN_H
3204 fprintf(stderr, " -h hostname | --host=hostname host or UNIX domain socket to connect to\n");
3205#else
3206 fprintf(stderr, " -h hostname | --host=hostname host to connect to\n");
3207#endif
3208 fprintf(stderr, " -p portnr | --port=portnr port to connect to\n");
3209 fprintf(stderr, " -u user | --user=user user id\n");
3210 fprintf(stderr, " -d database | --database=database database to connect to (may be URI)\n");
3211
3212 fprintf(stderr, " -e | --echo echo the query\n");
3213#ifdef HAVE_ICONV
3214 fprintf(stderr, " -E charset | --encoding=charset specify encoding (character set) of the terminal\n");
3215#endif
3216 fprintf(stderr, " -f kind | --format=kind specify output format {csv,tab,raw,sql,xml,trash,rowcount}\n");
3217 fprintf(stderr, " -H | --history load/save cmdline history (default off)\n");
3218 fprintf(stderr, " -i | --interactive interpret `\\' commands on stdin\n");
3219 fprintf(stderr, " -t | --timer=format use time formatting {none,clock,performance} (none is default)\n");
3220 fprintf(stderr, " -l language | --language=lang {sql,mal}\n");
3221 fprintf(stderr, " -L logfile | --log=logfile save client/server interaction\n");
3222 fprintf(stderr, " -s stmt | --statement=stmt run single statement\n");
3223 fprintf(stderr, " -X | --Xdebug trace mapi network interaction\n");
3224 fprintf(stderr, " -z | --timezone do not tell server our timezone\n");
3225#ifdef HAVE_POPEN
3226 fprintf(stderr, " -| cmd | --pager=cmd for pagination\n");
3227#endif
3228 fprintf(stderr, " -v | --version show version information and exit\n");
3229 fprintf(stderr, " -? | --help show this usage message\n");
3230
3231 fprintf(stderr, "\nSQL specific opions \n");
3232 fprintf(stderr, " -n nullstr | --null=nullstr change NULL representation for sql, csv and tab output modes\n");
3233 fprintf(stderr, " -a | --autocommit turn off autocommit mode\n");
3234 fprintf(stderr, " -R | --allow-remote allow remote content\n");
3235 fprintf(stderr, " -r nr | --rows=nr for pagination\n");
3236 fprintf(stderr, " -w nr | --width=nr for pagination\n");
3237 fprintf(stderr, " -D | --dump create an SQL dump\n");
3238 fprintf(stderr, " -N | --inserts use INSERT INTO statements when dumping\n");
3239 fprintf(stderr, "The file argument can be - for stdin\n");
3240 exit(xit);
3241}
3242
3243/* hardwired defaults, only used if monet environment cannot be found */
3244#define defaultPort 50000
3245
3246static inline bool
3247isfile(FILE *fp)
3248{
3249 struct stat stb;
3250 if (fstat(fileno(fp), &stb) < 0 ||
3251 (stb.st_mode & S_IFMT) != S_IFREG) {
3252 fclose(fp);
3253 return false;
3254 }
3255 return true;
3256}
3257
3258int
3259main(int argc, char **argv)
3260{
3261 int port = 0;
3262 char *user = NULL;
3263 char *passwd = NULL;
3264 char *host = NULL;
3265 char *command = NULL;
3266 char *dbname = NULL;
3267 char *output = NULL; /* output format as string */
3268 FILE *fp = NULL;
3269 bool trace = false;
3270 bool dump = false;
3271 bool useinserts = false;
3272 int c = 0;
3273 Mapi mid;
3274 int save_history = 0;
3275 bool interactive = false;
3276 bool has_fileargs = false;
3277 int option_index = 0;
3278 bool settz = true;
3279 bool autocommit = true; /* autocommit mode default on */
3280 bool user_set_as_flag = false;
3281 bool passwd_set_as_flag = false;
3282 static struct option long_options[] = {
3283 {"autocommit", 0, 0, 'a'},
3284 {"database", 1, 0, 'd'},
3285 {"dump", 0, 0, 'D'},
3286 {"inserts", 0, 0, 'N'},
3287 {"echo", 0, 0, 'e'},
3288#ifdef HAVE_ICONV
3289 {"encoding", 1, 0, 'E'},
3290#endif
3291 {"format", 1, 0, 'f'},
3292 {"help", 0, 0, '?'},
3293 {"history", 0, 0, 'H'},
3294 {"host", 1, 0, 'h'},
3295 {"interactive", 0, 0, 'i'},
3296 {"timer", 1, 0, 't'},
3297 {"language", 1, 0, 'l'},
3298 {"log", 1, 0, 'L'},
3299 {"null", 1, 0, 'n'},
3300#ifdef HAVE_POPEN
3301 {"pager", 1, 0, '|'},
3302#endif
3303 {"port", 1, 0, 'p'},
3304 {"rows", 1, 0, 'r'},
3305 {"statement", 1, 0, 's'},
3306 {"user", 1, 0, 'u'},
3307 {"version", 0, 0, 'v'},
3308 {"width", 1, 0, 'w'},
3309 {"Xdebug", 0, 0, 'X'},
3310 {"timezone", 0, 0, 'z'},
3311 {"allow-remote", 0, 0, 'R'},
3312 {0, 0, 0, 0}
3313 };
3314
3315#ifndef WIN32
3316 /* don't set locale on Windows: setting the locale like this
3317 * causes the output to be converted (we could set it to
3318 * ".OCP" if we knew for sure that we were running in a cmd
3319 * window) */
3320 if(setlocale(LC_CTYPE, "") == NULL) {
3321 fprintf(stderr, "error: could not set locale\n");
3322 exit(2);
3323 }
3324#endif
3325 toConsole = stdout_stream = file_wastream(stdout, "stdout");
3326 stderr_stream = file_wastream(stderr, "stderr");
3327 if(!stdout_stream || !stderr_stream) {
3328 if(stdout_stream)
3329 close_stream(stdout_stream);
3330 if(stderr_stream)
3331 close_stream(stderr_stream);
3332 fprintf(stderr, "error: could not open an output stream\n");
3333 exit(2);
3334 }
3335
3336 /* parse config file first, command line options override */
3337 parse_dotmonetdb(&user, &passwd, &dbname, &language, &save_history, &output, &pagewidth);
3338 pagewidthset = pagewidth != 0;
3339 if (language) {
3340 if (strcmp(language, "sql") == 0) {
3341 mode = SQL;
3342 } else if (strcmp(language, "mal") == 0) {
3343 mode = MAL;
3344 }
3345 } else {
3346 language = strdup("sql");
3347 mode = SQL;
3348 }
3349
3350 while ((c = getopt_long(argc, argv, "ad:De"
3351#ifdef HAVE_ICONV
3352 "E:"
3353#endif
3354 "f:h:Hil:L:n:Np:P:r:Rs:t:u:vw:Xz"
3355#ifdef HAVE_POPEN
3356 "|:"
3357#endif
3358 "?",
3359 long_options, &option_index)) != -1) {
3360 switch (c) {
3361 case 0:
3362 /* only needed for options that only have a
3363 * long form */
3364 break;
3365 case 'a':
3366 autocommit = false;
3367 break;
3368 case 'd':
3369 assert(optarg);
3370 if (dbname)
3371 free(dbname);
3372 dbname = strdup(optarg);
3373 break;
3374 case 'D':
3375 dump = true;
3376 break;
3377 case 'e':
3378 echoquery = true;
3379 break;
3380#ifdef HAVE_ICONV
3381 case 'E':
3382 assert(optarg);
3383 encoding = optarg;
3384 break;
3385#endif
3386 case 'f':
3387 assert(optarg);
3388 if (output != NULL)
3389 free(output);
3390 output = strdup(optarg); /* output format */
3391 break;
3392 case 'h':
3393 assert(optarg);
3394 host = optarg;
3395 break;
3396 case 'H':
3397 save_history = 1;
3398 break;
3399 case 'i':
3400 interactive = true;
3401 break;
3402 case 'l':
3403 assert(optarg);
3404 /* accept unambiguous prefix of language */
3405 if (strcmp(optarg, "sql") == 0 ||
3406 strcmp(optarg, "sq") == 0 ||
3407 strcmp(optarg, "s") == 0) {
3408 free(language);
3409 language = strdup(optarg);
3410 mode = SQL;
3411 } else if (strcmp(optarg, "mal") == 0 ||
3412 strcmp(optarg, "ma") == 0) {
3413 free(language);
3414 language = strdup("mal");
3415 mode = MAL;
3416 } else if (strcmp(optarg, "msql") == 0) {
3417 free(language);
3418 language = strdup("msql");
3419 mode = MAL;
3420 } else {
3421 fprintf(stderr, "language option needs to be sql or mal\n");
3422 exit(-1);
3423 }
3424 break;
3425 case 'L':
3426 assert(optarg);
3427 logfile = strdup(optarg);
3428 break;
3429 case 'n':
3430 assert(optarg);
3431 nullstring = optarg;
3432 break;
3433 case 'N':
3434 useinserts = true;
3435 break;
3436 case 'p':
3437 assert(optarg);
3438 port = atoi(optarg);
3439 break;
3440 case 'P':
3441 assert(optarg);
3442 if (passwd)
3443 free(passwd);
3444 passwd = strdup(optarg);
3445 passwd_set_as_flag = true;
3446 break;
3447 case 'r':
3448 assert(optarg);
3449 rowsperpage = atoi(optarg);
3450 break;
3451 case 'R':
3452 allow_remote = true;
3453 break;
3454 case 's':
3455 assert(optarg);
3456 command = optarg;
3457 break;
3458 case 't':
3459 if (optarg != NULL) {
3460 if (strcmp(optarg,"none") == 0) {
3461 timermode = T_NONE;
3462 } else if (strcmp(optarg,"clock") == 0) {
3463 timermode = T_CLOCK;
3464 } else if (strcmp(optarg, "perf") == 0 || strcmp(optarg, "performance") == 0) {
3465 timermode = T_PERF;
3466 } else if (*optarg != '\0') {
3467 fprintf(stderr, "warning: invalid argument to -t: %s\n",
3468 optarg);
3469 }
3470 }
3471 break;
3472 case 'u':
3473 assert(optarg);
3474 if (user)
3475 free(user);
3476 user = strdup(optarg);
3477 user_set_as_flag = true;
3478 break;
3479 case 'v': {
3480 mnstr_printf(toConsole,
3481 "mclient, the MonetDB interactive "
3482 "terminal, version %s", VERSION);
3483#ifdef MONETDB_RELEASE
3484 mnstr_printf(toConsole, " (%s)", MONETDB_RELEASE);
3485#else
3486 const char *rev = mercurial_revision();
3487 if (strcmp(rev, "Unknown") != 0)
3488 mnstr_printf(toConsole, " (hg id: %s)", rev);
3489#endif
3490 mnstr_printf(toConsole, "\n");
3491#ifdef HAVE_LIBREADLINE
3492 mnstr_printf(toConsole,
3493 "support for command-line editing "
3494 "compiled-in\n");
3495#endif
3496#ifdef HAVE_ICONV
3497#ifdef HAVE_NL_LANGINFO
3498 if (encoding == NULL)
3499 encoding = nl_langinfo(CODESET);
3500#endif
3501 mnstr_printf(toConsole,
3502 "character encoding: %s\n",
3503 encoding ? encoding : "utf-8 (default)");
3504#endif
3505 return 0;
3506 }
3507 case 'w':
3508 assert(optarg);
3509 pagewidth = atoi(optarg);
3510 pagewidthset = pagewidth != 0;
3511 break;
3512 case 'X':
3513 trace = true;
3514 break;
3515 case 'z':
3516 settz = false;
3517 break;
3518#ifdef HAVE_POPEN
3519 case '|':
3520 assert(optarg);
3521 pager = optarg;
3522 break;
3523#endif
3524 case '?':
3525 /* a bit of a hack: look at the option that the
3526 * current `c' is based on and see if we recognize
3527 * it: if -? or --help, exit with 0, else with -1 */
3528 usage(argv[0], strcmp(argv[optind - 1], "-?") == 0 || strcmp(argv[optind - 1], "--help") == 0 ? 0 : -1);
3529 /* not reached */
3530 default:
3531 usage(argv[0], -1);
3532 /* not reached */
3533 }
3534 }
3535 if (passwd_set_as_flag &&
3536 (output == NULL || strcmp(output, "test") != 0)) {
3537 usage(argv[0], -1);
3538 /* not reached */
3539 }
3540
3541#ifdef HAVE_ICONV
3542#ifdef HAVE_NL_LANGINFO
3543 if (encoding == NULL)
3544 encoding = nl_langinfo(CODESET);
3545#endif
3546 if (encoding != NULL && strcasecmp(encoding, "utf-8") == 0)
3547 encoding = NULL;
3548 if (encoding != NULL) {
3549 stream *s = iconv_wstream(toConsole, encoding, "stdout");
3550 if (s == NULL || mnstr_errnr(s)) {
3551 fprintf(stderr, "warning: cannot convert local character set %s to UTF-8\n", encoding);
3552 close_stream(s);
3553 } else
3554 toConsole = s;
3555 stdout_stream = toConsole;
3556 }
3557#endif /* HAVE_ICONV */
3558
3559 /* when config file would provide defaults */
3560 if (user_set_as_flag) {
3561 if (passwd && !passwd_set_as_flag) {
3562 free(passwd);
3563 passwd = NULL;
3564 }
3565 }
3566
3567 if (user == NULL)
3568 user = simple_prompt("user", BUFSIZ, 1, prompt_getlogin());
3569 if (passwd == NULL)
3570 passwd = simple_prompt("password", BUFSIZ, 0, NULL);
3571
3572 c = 0;
3573 has_fileargs = optind != argc;
3574
3575 if (dbname == NULL && has_fileargs &&
3576 ((fp = fopen(argv[optind], "r")) == NULL || !isfile(fp))) {
3577 fp = NULL;
3578 dbname = strdup(argv[optind]);
3579 optind++;
3580 has_fileargs = optind != argc;
3581 }
3582
3583 if (dbname != NULL && strncmp(dbname, "mapi:monetdb://", 15) == 0) {
3584 mid = mapi_mapiuri(dbname, user, passwd, language);
3585 } else {
3586 mid = mapi_mapi(host, port, user, passwd, language, dbname);
3587 }
3588 if (user)
3589 free(user);
3590 user = NULL;
3591 if (passwd)
3592 free(passwd);
3593 passwd = NULL;
3594 if (dbname)
3595 free(dbname);
3596 dbname = NULL;
3597 if (mid && mapi_error(mid) == MOK)
3598 mapi_reconnect(mid); /* actually, initial connect */
3599
3600 if (mid == NULL) {
3601 fprintf(stderr, "failed to allocate Mapi structure\n");
3602 exit(2);
3603 }
3604
3605 if (mapi_error(mid)) {
3606 if (trace)
3607 mapi_explain(mid, stderr);
3608 else
3609 fprintf(stderr, "%s\n", mapi_error_str(mid));
3610 exit(2);
3611 }
3612 mapi_cache_limit(mid, -1);
3613 if (dump) {
3614 if (mode == SQL) {
3615 exit(dump_database(mid, toConsole, 0, useinserts));
3616 } else {
3617 fprintf(stderr, "Dump only supported for SQL\n");
3618 exit(1);
3619 }
3620 }
3621
3622 struct privdata priv;
3623 priv = (struct privdata) {0};
3624 mapi_setfilecallback(mid, getfile, putfile, &priv);
3625
3626 if (!autocommit)
3627 mapi_setAutocommit(mid, autocommit);
3628
3629 if (logfile)
3630 mapi_log(mid, logfile);
3631
3632 mapi_trace(mid, trace);
3633 if (output) {
3634 setFormatter(output);
3635 free(output);
3636 } else {
3637 if (mode == SQL) {
3638 setFormatter("sql");
3639 } else {
3640 setFormatter("raw");
3641 }
3642 }
3643 /* give the user a welcome message with some general info */
3644 if (!has_fileargs && command == NULL && isatty(fileno(stdin))) {
3645 char *lang;
3646
3647 if (mode == SQL) {
3648 lang = "/SQL";
3649 } else {
3650 lang = "";
3651 }
3652
3653 mnstr_printf(toConsole,
3654 "Welcome to mclient, the MonetDB%s "
3655 "interactive terminal (%s)\n",
3656 lang,
3657#ifdef MONETDB_RELEASE
3658 MONETDB_RELEASE
3659#else
3660 "unreleased"
3661#endif
3662 );
3663
3664 if (mode == SQL)
3665 dump_version(mid, toConsole, "Database:");
3666
3667 mnstr_printf(toConsole, "FOLLOW US on https://twitter.com/MonetDB ");
3668 mnstr_printf(toConsole, "or https://github.com/MonetDB/MonetDB\n");
3669
3670 mnstr_printf(toConsole, "Type \\q to quit, \\? for a list of available commands\n");
3671 if (mode == SQL)
3672 mnstr_printf(toConsole, "auto commit mode: %s\n",
3673 mapi_get_autocommit(mid) ? "on" : "off");
3674 }
3675
3676 if (mode == SQL && settz)
3677 set_timezone(mid);
3678
3679 if (command != NULL) {
3680#ifdef HAVE_ICONV
3681 iconv_t cd_in;
3682 int free_command = 0;
3683
3684 if (encoding != NULL &&
3685 (cd_in = iconv_open("utf-8", encoding)) != (iconv_t) -1) {
3686 char *savecommand = command;
3687 ICONV_CONST char *from = command;
3688 size_t fromlen = strlen(from);
3689 int factor = 4;
3690 size_t tolen = factor * fromlen + 1;
3691 char *to = malloc(tolen);
3692
3693 if (to == NULL) {
3694 fprintf(stderr,"Malloc in main failed");
3695 exit(2);
3696 }
3697 free_command = 1;
3698
3699 try_again:
3700 command = to;
3701 if (iconv(cd_in, &from, &fromlen, &to, &tolen) == (size_t) -1) {
3702 switch (errno) {
3703 case EILSEQ:
3704 /* invalid multibyte sequence */
3705 fprintf(stderr, "Illegal input sequence in command line\n");
3706 exit(-1);
3707 case E2BIG:
3708 /* output buffer too small */
3709 from = savecommand;
3710 fromlen = strlen(from);
3711 factor *= 2;
3712 tolen = factor * fromlen + 1;
3713 free(command);
3714 to = malloc(tolen);
3715 if (to == NULL) {
3716 fprintf(stderr,"Malloc in main failed");
3717 exit(2);
3718 }
3719 goto try_again;
3720 case EINVAL:
3721 /* incomplete multibyte sequence */
3722 fprintf(stderr, "Incomplete input sequence on command line\n");
3723 exit(-1);
3724 default:
3725 break;
3726 }
3727 }
3728 *to = 0;
3729 iconv_close(cd_in);
3730 } else if (encoding)
3731 fprintf(stderr, "warning: cannot convert local character set %s to UTF-8\n", encoding);
3732#endif
3733 /* execute from command-line, need interactive to know whether
3734 * to keep the mapi handle open */
3735 timerStart();
3736 c = doRequest(mid, command);
3737 timerEnd();
3738#ifdef HAVE_ICONV
3739 if (free_command)
3740 free(command);
3741#endif
3742 }
3743
3744 if (optind < argc) {
3745 /* execute from file(s) */
3746 while (optind < argc) {
3747 stream *s;
3748
3749 if (fp == NULL &&
3750 (fp = (strcmp(argv[optind], "-") == 0 ?
3751 stdin :
3752 fopen(argv[optind], "r"))) == NULL) {
3753 fprintf(stderr, "%s: cannot open\n", argv[optind]);
3754 c |= 1;
3755 } else if ((s = file_rastream(fp, argv[optind])) == NULL) {
3756 fclose(fp);
3757 c |= 1;
3758 } else {
3759 c |= doFile(mid, s, useinserts, interactive, save_history);
3760 }
3761 fp = NULL;
3762 optind++;
3763 }
3764 } else if (command && mapi_get_active(mid))
3765 c = doFileBulk(mid, NULL);
3766
3767 if (!has_fileargs && command == NULL) {
3768 stream *s = file_rastream(stdin, "<stdin>");
3769 if(!s) {
3770 mapi_destroy(mid);
3771 mnstr_destroy(stdout_stream);
3772 mnstr_destroy(stderr_stream);
3773 fprintf(stderr,"Failed to open stream for stdin\n");
3774 exit(2);
3775 }
3776 c = doFile(mid, s, useinserts, interactive, save_history);
3777 }
3778
3779 mapi_destroy(mid);
3780 mnstr_destroy(stdout_stream);
3781 mnstr_destroy(stderr_stream);
3782 if (priv.buf != NULL)
3783 free(priv.buf);
3784
3785 return c;
3786}
3787