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