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 228920 : 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 228920 : static struct timeval tpbase; /* automatically initialized to 0 */
195 228920 : struct timeval tp;
196 :
197 228920 : if (tpbase.tv_sec == 0)
198 153 : gettimeofday(&tpbase, NULL);
199 228920 : gettimeofday(&tp, NULL);
200 228920 : tp.tv_sec -= tpbase.tv_sec;
201 228920 : 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 5535 : timerStart(void)
221 : {
222 11070 : t0 = gettime();
223 0 : }
224 :
225 : static void
226 108986 : timerPause(void)
227 : {
228 108986 : t1 = gettime();
229 108986 : if (t0 == 0)
230 137 : t0 = t1;
231 108986 : }
232 :
233 : static void
234 103327 : timerResume(void)
235 : {
236 103327 : if (t1 == 0)
237 0 : t1 = gettime();
238 103327 : assert(t1 >= t0);
239 103327 : t0 = gettime() - (t1 - t0);
240 103327 : }
241 :
242 : static void
243 5535 : timerEnd(void)
244 : {
245 5535 : mnstr_flush(toConsole, MNSTR_FLUSH_DATA);
246 5535 : t1 = gettime();
247 5535 : assert(t1 >= t0);
248 5535 : }
249 :
250 : static timertype th = 0;
251 : static void
252 5537 : timerHumanStop(void)
253 : {
254 11074 : 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 9857 : timerHuman(int64_t sqloptimizer, int64_t maloptimizer, int64_t querytime, bool singleinstr, bool total)
266 : {
267 9857 : timertype t = th - t0;
268 :
269 9857 : 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 9857 : 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 9857 : 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 66345 : fetch_line(MapiHdl hdl)
334 : {
335 66345 : char *reply;
336 :
337 66345 : if ((reply = mapi_fetch_line(hdl)) == NULL)
338 : return NULL;
339 62454 : 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 108518 : SQLsetSpecial(const char *command)
362 : {
363 108518 : if (mode == SQL && command && specials == NOmodifier) {
364 : /* catch the specials for better rendering */
365 112804 : while (*command == ' ' || *command == '\t')
366 4286 : command++;
367 108518 : if (strncmp(command, "debug", 5) == 0)
368 0 : specials = DEBUGmodifier;
369 : else
370 108518 : specials = NOmodifier;
371 : }
372 108518 : }
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 4930 : SQLqueryEcho(MapiHdl hdl)
896 : {
897 4930 : 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 3400 : while ((q = strchr(q, '\n')) != NULL) {
906 2346 : *q++ = '\0';
907 2346 : mnstr_printf(toConsole, "#%s\n", p);
908 2346 : 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 4930 : }
927 :
928 : /* state machine to recognize integers, floating point numbers, OIDs */
929 : static char *
930 0 : 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 0 : int state = 0;
949 :
950 0 : if ((l == 4 && strcmp(s, "true") == 0) ||
951 0 : (l == 5 && strcmp(s, "false") == 0))
952 : return "bit";
953 0 : while (l != 0) {
954 0 : if (*s == 0)
955 : return "str";
956 0 : 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 0 : switch (state) {
973 0 : case 0:
974 : case 1:
975 0 : state = 2; /* digit after optional sign */
976 0 : 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 0 : s++;
1043 0 : l--;
1044 : }
1045 0 : switch (state) {
1046 : case 13:
1047 : return "oid";
1048 0 : case 2:
1049 0 : 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 3792 : TESTrenderer(MapiHdl hdl)
1063 : {
1064 3792 : int fields;
1065 3792 : char *reply;
1066 3792 : char *s;
1067 3792 : size_t l;
1068 3792 : char *tp;
1069 3792 : char *sep;
1070 3792 : int i;
1071 :
1072 65754 : while (mnstr_errnr(toConsole) == MNSTR_NO__ERROR && (reply = fetch_line(hdl)) != 0) {
1073 61962 : if (*reply != '[') {
1074 15168 : if (*reply == '=')
1075 0 : reply++;
1076 15168 : mnstr_printf(toConsole, "%s\n", reply);
1077 15168 : continue;
1078 : }
1079 46794 : fields = mapi_split_line(hdl);
1080 46794 : sep = "[ ";
1081 1416799 : for (i = 0; i < fields; i++) {
1082 1323211 : s = mapi_fetch_field(hdl, i);
1083 1323211 : l = mapi_fetch_field_len(hdl, i);
1084 1323211 : tp = mapi_get_type(hdl, i);
1085 1323211 : if (strcmp(tp, "unknown") == 0)
1086 0 : tp = classify(s, l);
1087 1323211 : mnstr_printf(toConsole, "%s", sep);
1088 1323211 : sep = ",\t";
1089 1323211 : if (s == NULL)
1090 790992 : mnstr_printf(toConsole, "%s", mode == SQL ? "NULL" : "nil");
1091 532219 : else if (strcmp(tp, "varchar") == 0 ||
1092 155599 : strcmp(tp, "char") == 0 ||
1093 155599 : strcmp(tp, "clob") == 0 ||
1094 155599 : strcmp(tp, "str") == 0 ||
1095 155599 : strcmp(tp, "json") == 0 ||
1096 : /* NULL byte in string? */
1097 155599 : strlen(s) < l ||
1098 : /* start or end with white space? */
1099 155599 : my_isspace(*s) ||
1100 155599 : (l > 0 && my_isspace(s[l - 1])) ||
1101 : /* timezone can have embedded comma */
1102 155599 : strcmp(tp, "timezone") == 0 ||
1103 : /* a bunch of geom types */
1104 155599 : strcmp(tp, "curve") == 0 ||
1105 155599 : strcmp(tp, "geometry") == 0 ||
1106 155599 : strcmp(tp, "linestring") == 0 ||
1107 155599 : strcmp(tp, "mbr") == 0 ||
1108 155599 : strcmp(tp, "multilinestring") == 0 ||
1109 155599 : strcmp(tp, "point") == 0 ||
1110 155599 : strcmp(tp, "polygon") == 0 ||
1111 155599 : strcmp(tp, "surface") == 0) {
1112 376620 : mnstr_printf(toConsole, "\"");
1113 4500482 : while (l != 0) {
1114 4123862 : 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 26027 : case '"':
1128 26027 : mnstr_write(toConsole, "\\\"", 1, 2);
1129 26027 : break;
1130 31621 : 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 31621 : if (strcmp(tp, "curve") == 0 ||
1141 31621 : strcmp(tp, "geometry") == 0 ||
1142 31621 : strcmp(tp, "linestring") == 0 ||
1143 31621 : strcmp(tp, "mbr") == 0 ||
1144 31621 : strcmp(tp, "multilinestring") == 0 ||
1145 31621 : strcmp(tp, "point") == 0 ||
1146 31621 : strcmp(tp, "polygon") == 0 ||
1147 31621 : 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 4097727 : if ((unsigned char) *s < ' ')
1161 0 : mnstr_printf(toConsole,
1162 : "\\%03o",
1163 : (unsigned char) *s);
1164 : else
1165 4097727 : mnstr_write(toConsole, s, 1, 1);
1166 : break;
1167 : }
1168 4123862 : s++;
1169 4123862 : l--;
1170 : }
1171 376620 : mnstr_write(toConsole, "\"", 1, 1);
1172 155599 : } else if (strcmp(tp, "double") == 0 ||
1173 155599 : 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 1 : if (v > (double) 999999999999999 ||
1181 1 : v < (double) -999999999999999 ||
1182 1 : (double) (int) v != v ||
1183 0 : snprintf(buf, sizeof(buf), "%.0f", v) <= 0 ||
1184 0 : strtod(buf, NULL) != v) {
1185 1 : for (j = 4; j < 11; j++) {
1186 1 : snprintf(buf, sizeof(buf), "%.*g", j, v);
1187 1 : if (v == strtod(buf, NULL))
1188 : break;
1189 : }
1190 : }
1191 1 : mnstr_printf(toConsole, "%s", buf);
1192 155598 : } else if (strcmp(tp, "real") == 0) {
1193 0 : char buf[32];
1194 0 : int j;
1195 0 : float v;
1196 0 : if (strcmp(s, "-0") == 0) /* normalize -0 */
1197 0 : s = "0";
1198 0 : v = strtof(s, NULL);
1199 0 : if (v > (float) 9999999 ||
1200 0 : v < (float) -9999999 ||
1201 0 : (float) (int) v != v ||
1202 0 : snprintf(buf, sizeof(buf), "%.0f", v) <= 0 ||
1203 0 : strtof(buf, NULL) != v) {
1204 0 : for (j = 4; j < 6; j++) {
1205 0 : snprintf(buf, sizeof(buf), "%.*g", j, v);
1206 0 : if (v == strtof(buf, NULL))
1207 : break;
1208 : }
1209 : }
1210 0 : mnstr_printf(toConsole, "%s", buf);
1211 : } else
1212 155598 : mnstr_printf(toConsole, "%s", s);
1213 : }
1214 46794 : mnstr_printf(toConsole, "\t]\n");
1215 : }
1216 3792 : }
1217 :
1218 : static void
1219 3 : RAWrenderer(MapiHdl hdl)
1220 : {
1221 3 : char *line;
1222 :
1223 24 : while ((line = fetch_line(hdl)) != 0) {
1224 21 : if (*line == '=')
1225 0 : line++;
1226 21 : mnstr_printf(toConsole, "%s\n", line);
1227 : }
1228 3 : }
1229 :
1230 : static int
1231 4 : SQLheader(MapiHdl hdl, int *len, int fields, char more)
1232 : {
1233 4 : int rows = 1; /* start with the separator row */
1234 4 : SQLseparator(len, fields, '-');
1235 4 : if (mapi_get_name(hdl, 0)) {
1236 4 : int i;
1237 4 : char **names = (char **) malloc(fields * sizeof(char *));
1238 4 : int *numeric = (int *) malloc(fields * sizeof(int));
1239 :
1240 4 : if (names == NULL || numeric == NULL) {
1241 0 : free(names);
1242 0 : free(numeric);
1243 0 : fprintf(stderr,"Malloc for SQLheader failed");
1244 0 : exit(2);
1245 : }
1246 8 : for (i = 0; i < fields; i++) {
1247 4 : names[i] = mapi_get_name(hdl, i);
1248 4 : numeric[i] = 0;
1249 : }
1250 4 : rows += SQLrow(len, numeric, names, fields, 1, more);
1251 4 : rows++; /* add a separator row */
1252 4 : SQLseparator(len, fields, '=');
1253 4 : free(names);
1254 4 : free(numeric);
1255 : }
1256 4 : return rows;
1257 : }
1258 :
1259 : static void
1260 0 : SQLdebugRendering(MapiHdl hdl)
1261 : {
1262 0 : char *reply;
1263 0 : int cnt = 0;
1264 :
1265 0 : snprintf(promptbuf, sizeof(promptbuf), "mdb>");
1266 0 : while ((reply = fetch_line(hdl))) {
1267 0 : cnt++;
1268 0 : mnstr_printf(toConsole, "%s\n", reply);
1269 0 : if (strncmp(reply, "mdb>#EOD", 8) == 0) {
1270 0 : cnt = 0;
1271 0 : while ((reply = fetch_line(hdl)))
1272 0 : mnstr_printf(toConsole, "%s\n", reply);
1273 : break;
1274 : }
1275 : }
1276 0 : if (cnt == 0) {
1277 0 : setPrompt();
1278 0 : specials = NOmodifier;
1279 : }
1280 0 : }
1281 :
1282 : static void
1283 0 : SQLpagemove(int *len, int fields, int *ps, bool *skiprest)
1284 : {
1285 0 : char buf[512];
1286 0 : ssize_t sz;
1287 :
1288 0 : SQLseparator(len, fields, '-');
1289 0 : mnstr_printf(toConsole, "next page? (continue,quit,next)");
1290 0 : mnstr_flush(toConsole, MNSTR_FLUSH_DATA);
1291 0 : sz = mnstr_readline(fromConsole, buf, sizeof(buf));
1292 0 : if (sz < 0 && mnstr_errnr(fromConsole) == MNSTR_INTERRUPT) {
1293 : /* interrupted, equivalent to typing 'q' */
1294 0 : mnstr_clearerr(fromConsole);
1295 0 : mnstr_printf(toConsole, "\n");
1296 0 : *skiprest = true;
1297 0 : } else if (sz > 0) {
1298 0 : if (buf[0] == 'c')
1299 0 : *ps = 0;
1300 0 : if (buf[0] == 'q')
1301 0 : *skiprest = true;
1302 : /* make sure we read the whole line */
1303 0 : while (sz > 0 && buf[sz - 1] != '\n')
1304 0 : sz = mnstr_readline(fromConsole, buf, sizeof(buf));
1305 : }
1306 0 : if (!*skiprest)
1307 0 : SQLseparator(len, fields, '-');
1308 0 : }
1309 :
1310 : static volatile sig_atomic_t state;
1311 : #define READING 1
1312 : #define WRITING 2
1313 : #define QUERYING 3
1314 : #define IDLING 0
1315 : #define INTERRUPT (-1)
1316 :
1317 : static void
1318 0 : sigint_handler(int signum)
1319 : {
1320 0 : (void) signum;
1321 :
1322 0 : state = INTERRUPT;
1323 : #ifndef HAVE_SIGACTION
1324 : if (signal(signum, sigint_handler) == SIG_ERR)
1325 : perror("Could not reinstall signal handler");
1326 : #endif
1327 : #ifdef HAVE_LIBREADLINE
1328 0 : readline_int_handler();
1329 : #endif
1330 0 : }
1331 :
1332 : static void
1333 4 : SQLrenderer(MapiHdl hdl)
1334 : {
1335 4 : int i, total, lentotal, vartotal, minvartotal;
1336 4 : int fields, rfields, printfields = 0, max = 1, graphwaste = 0;
1337 4 : int *len = NULL, *hdr = NULL, *numeric = NULL;
1338 4 : char **rest = NULL;
1339 4 : int ps = rowsperpage;
1340 4 : bool skiprest = false;
1341 4 : int64_t rows; /* total number of rows */
1342 :
1343 4 : if (ps == 0)
1344 0 : ps = pageheight;
1345 4 : croppedfields = 0;
1346 4 : fields = mapi_get_field_count(hdl);
1347 4 : rows = mapi_get_row_count(hdl);
1348 :
1349 4 : len = calloc(fields, sizeof(*len));
1350 4 : hdr = calloc(fields, sizeof(*hdr));
1351 4 : rest = calloc(fields, sizeof(*rest));
1352 4 : numeric = calloc(fields, sizeof(*numeric));
1353 4 : if (len == NULL || hdr == NULL || rest == NULL || numeric == NULL) {
1354 0 : if (len)
1355 0 : free(len);
1356 0 : if (hdr)
1357 0 : free(hdr);
1358 0 : if (rest)
1359 0 : free(rest);
1360 0 : if (numeric)
1361 0 : free(numeric);
1362 0 : fprintf(stderr,"Malloc for SQLrenderer failed");
1363 0 : exit(2);
1364 : }
1365 :
1366 4 : if (state == INTERRUPT) {
1367 0 : free(len);
1368 0 : free(hdr);
1369 0 : free(rest);
1370 0 : free(numeric);
1371 0 : return;
1372 : }
1373 4 : state = WRITING;
1374 :
1375 4 : total = 0;
1376 4 : lentotal = 0;
1377 4 : vartotal = 0;
1378 4 : minvartotal = 0;
1379 8 : for (i = 0; i < fields; i++) {
1380 4 : char *s;
1381 :
1382 4 : len[i] = mapi_get_len(hdl, i);
1383 4 : if (len[i] == 0) {
1384 0 : if ((s = mapi_get_type(hdl, i)) == NULL ||
1385 0 : (strcmp(s, "varchar") != 0 &&
1386 0 : strcmp(s, "clob") != 0 &&
1387 0 : strcmp(s, "char") != 0 &&
1388 0 : strcmp(s, "str") != 0 &&
1389 0 : strcmp(s, "json") != 0)) {
1390 : /* no table width known, use maximum,
1391 : * rely on squeezing later on to fix
1392 : * it to whatever is available; note
1393 : * that for a column type of varchar,
1394 : * 0 means the complete column is NULL
1395 : * or empty string, so MINCOLSIZE
1396 : * (below) will work great */
1397 0 : len[i] = pagewidth <= 0 ? DEFWIDTH : pagewidth;
1398 0 : } else if (strcmp(s, "uuid") == 0) {
1399 : /* we know how large the UUID representation
1400 : * is, even if the server doesn't */
1401 0 : len[i] = 36;
1402 : }
1403 : }
1404 4 : if (len[i] < MINCOLSIZE)
1405 0 : len[i] = MINCOLSIZE;
1406 4 : s = mapi_get_name(hdl, i);
1407 4 : if (s != NULL) {
1408 4 : size_t l = strlen(s);
1409 4 : assert(l <= INT_MAX);
1410 4 : hdr[i] = (int) l;
1411 : } else {
1412 0 : hdr[i] = 0;
1413 : }
1414 : /* if no rows, just try to draw headers nicely */
1415 4 : if (rows == 0)
1416 0 : len[i] = hdr[i];
1417 4 : s = mapi_get_type(hdl, i);
1418 4 : numeric[i] = s != NULL &&
1419 4 : (strcmp(s, "int") == 0 ||
1420 4 : strcmp(s, "tinyint") == 0 ||
1421 4 : strcmp(s, "bigint") == 0 ||
1422 4 : strcmp(s, "hugeint") == 0 ||
1423 4 : strcmp(s, "oid") == 0 ||
1424 4 : strcmp(s, "smallint") == 0 ||
1425 4 : strcmp(s, "double") == 0 ||
1426 4 : strcmp(s, "float") == 0 ||
1427 4 : strcmp(s, "decimal") == 0);
1428 :
1429 4 : if (rows == 0) {
1430 0 : minvartotal += len[i]; /* don't wrap column headers if no data */
1431 4 : } else if (numeric[i]) {
1432 : /* minimum size is equal to maximum size */
1433 0 : minvartotal += len[i];
1434 : } else {
1435 : /* minimum size for wide columns is MINVARCOLSIZE */
1436 4 : minvartotal += len[i] > MINVARCOLSIZE ? MINVARCOLSIZE : len[i];
1437 : }
1438 4 : vartotal += len[i];
1439 4 : total += len[i];
1440 :
1441 : /* do a very pessimistic calculation to determine if more
1442 : * columns would actually fit on the screen */
1443 4 : if (pagewidth > 0 &&
1444 0 : ((((printfields + 1) * 3) - 1) + 2) + /* graphwaste */
1445 0 : (total - vartotal) + minvartotal > pagewidth) {
1446 : /* this last column was too much */
1447 : total -= len[i];
1448 : if (!numeric[i])
1449 4 : vartotal -= len[i];
1450 : break;
1451 : }
1452 :
1453 4 : lentotal += (hdr[i] > len[i] ? hdr[i] : len[i]);
1454 4 : printfields++;
1455 : }
1456 :
1457 : /* what we waste on space on the display is the column separators '
1458 : * | ', but the edges lack the edgespace of course */
1459 4 : graphwaste = ((printfields * 3) - 1) + 2;
1460 : /* make sure we can indicate we dropped columns */
1461 4 : if (fields != printfields)
1462 0 : graphwaste++;
1463 :
1464 : /* punish the column headers first until you cannot squeeze any
1465 : * further */
1466 4 : while (pagewidth > 0 && graphwaste + lentotal > pagewidth) {
1467 : /* pick the column where the header is longest compared to its
1468 : * content */
1469 : max = -1;
1470 0 : for (i = 0; i < printfields; i++) {
1471 0 : if (hdr[i] > len[i]) {
1472 0 : if (max == -1 ||
1473 0 : hdr[max] - len[max] < hdr[i] - len[i])
1474 0 : max = i;
1475 : }
1476 : }
1477 0 : if (max == -1)
1478 : break;
1479 0 : hdr[max]--;
1480 0 : lentotal--;
1481 : }
1482 :
1483 : /* correct the lengths if the headers are wider than the content,
1484 : * since the headers are maximally squeezed to the content above, if
1485 : * a header is larger than its content, it means there was space
1486 : * enough. If not, the content will be squeezed below. */
1487 8 : for (i = 0; i < printfields; i++)
1488 4 : if (len[i] < hdr[i])
1489 0 : len[i] = hdr[i];
1490 :
1491 : /* worst case: lentotal = total, which means it still doesn't fit,
1492 : * values will be squeezed next */
1493 4 : while (pagewidth > 0 && graphwaste + total > pagewidth) {
1494 : max = -1;
1495 0 : for (i = 0; i < printfields; i++) {
1496 0 : if (!numeric[i] && (max == -1 || len[i] > len[max]))
1497 0 : max = i;
1498 : }
1499 :
1500 : /* no varsized fields that we can squeeze */
1501 0 : if (max == -1)
1502 : break;
1503 : /* penalty for largest field */
1504 0 : len[max]--;
1505 0 : total--;
1506 : /* no more squeezing possible */
1507 0 : if (len[max] == 1)
1508 : break;
1509 : }
1510 :
1511 4 : int64_t lines; /* count number of lines printed for pager */
1512 4 : lines = SQLheader(hdl, len, printfields, fields != printfields);
1513 :
1514 4 : int64_t nrows = 0; /* count number of rows printed */
1515 11 : while ((rfields = fetch_row(hdl)) != 0) {
1516 7 : if (mnstr_errnr(toConsole) != MNSTR_NO__ERROR)
1517 0 : continue;
1518 7 : if (rfields != fields) {
1519 0 : mnstr_printf(stderr_stream,
1520 : "invalid tuple received from server, "
1521 : "got %d columns, expected %d, ignoring\n", rfields, fields);
1522 0 : continue;
1523 : }
1524 7 : if (skiprest)
1525 0 : continue;
1526 14 : for (i = 0; i < printfields; i++) {
1527 7 : rest[i] = mapi_fetch_field(hdl, i);
1528 7 : if (rest[i] == NULL)
1529 0 : rest[i] = nullstring;
1530 : else {
1531 : char *p = rest[i];
1532 :
1533 7 : while ((p = strchr(p, '\r')) != 0) {
1534 0 : switch (p[1]) {
1535 0 : case '\0':
1536 : /* end of string: remove CR */
1537 0 : *p = 0;
1538 0 : break;
1539 0 : case '\n':
1540 : /* followed by LF: remove CR */
1541 : /* note: copy including NUL */
1542 0 : memmove(p, p + 1, strlen(p));
1543 0 : break;
1544 0 : default:
1545 : /* replace with ' ' */
1546 0 : *p = ' ';
1547 0 : break;
1548 : }
1549 : }
1550 : }
1551 : }
1552 :
1553 7 : if (ps > 0 && lines >= ps && fromConsole != NULL) {
1554 0 : SQLpagemove(len, printfields, &ps, &skiprest);
1555 0 : if (skiprest) {
1556 0 : mapi_finish(hdl);
1557 0 : break;
1558 : }
1559 : lines = 0;
1560 : }
1561 :
1562 7 : if (state == INTERRUPT) {
1563 0 : skiprest = true;
1564 0 : mapi_finish(hdl);
1565 0 : break;
1566 : }
1567 :
1568 7 : nrows++;
1569 7 : lines += SQLrow(len, numeric, rest, printfields, 2, 0);
1570 : }
1571 4 : state = IDLING;
1572 4 : if (fields && !skiprest)
1573 4 : SQLseparator(len, printfields, '-');
1574 4 : if (skiprest)
1575 0 : mnstr_printf(toConsole, "%" PRId64 " of %" PRId64 " tuple%s", nrows, rows, nrows != 1 ? "s" : "");
1576 : else
1577 5 : mnstr_printf(toConsole, "%" PRId64 " tuple%s", rows, rows != 1 ? "s" : "");
1578 :
1579 4 : if (fields != printfields || croppedfields > 0)
1580 0 : mnstr_printf(toConsole, " !");
1581 4 : if (fields != printfields) {
1582 0 : rows = fields - printfields;
1583 0 : mnstr_printf(toConsole, "%" PRId64 " column%s dropped", rows, rows != 1 ? "s" : "");
1584 : }
1585 4 : if (fields != printfields && croppedfields > 0)
1586 0 : mnstr_printf(toConsole, ", ");
1587 4 : if (croppedfields > 0)
1588 0 : mnstr_printf(toConsole, "%d field%s truncated",
1589 : croppedfields, croppedfields != 1 ? "s" : "");
1590 4 : if (fields != printfields || croppedfields > 0) {
1591 0 : mnstr_printf(toConsole, "!");
1592 0 : if (firstcrop) {
1593 0 : firstcrop = false;
1594 0 : mnstr_printf(toConsole, "\nnote: to disable dropping columns and/or truncating fields use \\w-1");
1595 : }
1596 : }
1597 4 : mnstr_printf(toConsole, "\n");
1598 :
1599 4 : free(len);
1600 4 : free(hdr);
1601 4 : free(rest);
1602 4 : free(numeric);
1603 : }
1604 :
1605 : static void
1606 175 : setFormatter(const char *s)
1607 : {
1608 175 : if (separator)
1609 0 : free(separator);
1610 175 : separator = NULL;
1611 175 : csvheader = false;
1612 175 : noquote = false;
1613 : #ifdef _TWO_DIGIT_EXPONENT
1614 : if (formatter == TESTformatter)
1615 : _set_output_format(0);
1616 : #endif
1617 175 : if (strcmp(s, "sql") == 0) {
1618 20 : formatter = TABLEformatter;
1619 155 : } else if (strcmp(s, "csv") == 0) {
1620 56 : formatter = CSVformatter;
1621 56 : separator = strdup(",");
1622 99 : } else if (strncmp(s, "csv=", 4) == 0) {
1623 0 : formatter = CSVformatter;
1624 0 : if (s[4] == '"') {
1625 0 : separator = strdup(s + 5);
1626 0 : if (separator[strlen(separator) - 1] == '"')
1627 0 : separator[strlen(separator) - 1] = 0;
1628 : } else
1629 0 : separator = strdup(s + 4);
1630 99 : } else if (strncmp(s, "csv+", 4) == 0) {
1631 0 : formatter = CSVformatter;
1632 0 : if (s[4] == '"') {
1633 0 : separator = strdup(s + 5);
1634 0 : if (separator[strlen(separator) - 1] == '"')
1635 0 : separator[strlen(separator) - 1] = 0;
1636 : } else
1637 0 : separator = strdup(s + 4);
1638 0 : csvheader = true;
1639 99 : } else if (strcmp(s, "csv-noquote") == 0) {
1640 0 : noquote = true;
1641 0 : formatter = CSVformatter;
1642 0 : separator = strdup(",");
1643 99 : } else if (strncmp(s, "csv-noquote=", 12) == 0) {
1644 0 : noquote = true;
1645 0 : formatter = CSVformatter;
1646 0 : if (s[12] == '"') {
1647 0 : separator = strdup(s + 13);
1648 0 : if (separator[strlen(separator) - 1] == '"')
1649 0 : separator[strlen(separator) - 1] = 0;
1650 : } else
1651 0 : separator = strdup(s + 12);
1652 99 : } else if (strncmp(s, "csv-noquote+", 12) == 0) {
1653 0 : noquote = true;
1654 0 : formatter = CSVformatter;
1655 0 : if (s[12] == '"') {
1656 0 : separator = strdup(s + 13);
1657 0 : if (separator[strlen(separator) - 1] == '"')
1658 0 : separator[strlen(separator) - 1] = 0;
1659 : } else
1660 0 : separator = strdup(s + 12);
1661 0 : csvheader = true;
1662 99 : } else if (strcmp(s, "tab") == 0) {
1663 0 : formatter = CSVformatter;
1664 0 : separator = strdup("\t");
1665 99 : } else if (strcmp(s, "raw") == 0) {
1666 9 : formatter = RAWformatter;
1667 90 : } else if (strcmp(s, "xml") == 0) {
1668 0 : formatter = XMLformatter;
1669 90 : } else if (strcmp(s, "test") == 0) {
1670 : #ifdef _TWO_DIGIT_EXPONENT
1671 : _set_output_format(_TWO_DIGIT_EXPONENT);
1672 : #endif
1673 84 : formatter = TESTformatter;
1674 6 : } else if (strcmp(s, "trash") == 0) {
1675 2 : formatter = TRASHformatter;
1676 4 : } else if (strcmp(s, "rowcount") == 0) {
1677 0 : formatter = ROWCOUNTformatter;
1678 4 : } else if (strcmp(s, "x") == 0 || strcmp(s, "expanded") == 0) {
1679 4 : formatter = EXPANDEDformatter;
1680 : } else {
1681 0 : mnstr_printf(toConsole, "unsupported formatter\n");
1682 : }
1683 175 : }
1684 :
1685 : static void
1686 5535 : setWidth(void)
1687 : {
1688 5535 : if (!pagewidthset) {
1689 : #ifdef TIOCGWINSZ
1690 5535 : struct winsize ws;
1691 :
1692 5535 : if (ioctl(fileno(stdout), TIOCGWINSZ, &ws) == 0 && ws.ws_col > 0) {
1693 0 : pagewidth = ws.ws_col;
1694 0 : pageheight = ws.ws_row;
1695 : } else
1696 : #endif
1697 : {
1698 5535 : pagewidth = pageheight = -1;
1699 : }
1700 : }
1701 5535 : }
1702 :
1703 : #ifdef HAVE_POPEN
1704 : static void
1705 5535 : start_pager(stream **saveFD)
1706 : {
1707 5535 : *saveFD = NULL;
1708 :
1709 5535 : if (pager) {
1710 0 : FILE *p;
1711 :
1712 0 : p = popen(pager, "w");
1713 0 : if (p == NULL)
1714 0 : fprintf(stderr, "Starting '%s' failed\n", pager);
1715 : else {
1716 0 : *saveFD = toConsole;
1717 : /* put | in name to indicate that file should be closed with pclose */
1718 0 : if ((toConsole = file_wstream(p, false, "|pager")) == NULL) {
1719 0 : toConsole = *saveFD;
1720 0 : *saveFD = NULL;
1721 0 : fprintf(stderr, "Starting '%s' failed\n", pager);
1722 : }
1723 : #ifdef HAVE_ICONV
1724 0 : if (encoding != NULL) {
1725 0 : if ((toConsole = iconv_wstream(toConsole, encoding, "pager")) == NULL) {
1726 0 : toConsole = *saveFD;
1727 0 : *saveFD = NULL;
1728 0 : fprintf(stderr, "Starting '%s' failed\n", pager);
1729 : }
1730 : }
1731 : #endif
1732 : }
1733 : }
1734 5535 : }
1735 :
1736 : static void
1737 5535 : end_pager(stream *saveFD)
1738 : {
1739 5535 : if (saveFD) {
1740 0 : close_stream(toConsole);
1741 0 : toConsole = saveFD;
1742 : }
1743 5535 : }
1744 : #endif
1745 :
1746 : static int
1747 5535 : format_result(Mapi mid, MapiHdl hdl, bool singleinstr)
1748 : {
1749 5535 : MapiMsg rc = MERROR;
1750 5535 : int64_t aff, lid;
1751 5535 : char *reply;
1752 5535 : int64_t sqloptimizer = 0;
1753 5535 : int64_t maloptimizer = 0;
1754 5535 : int64_t querytime = 0;
1755 5535 : int64_t rows = 0;
1756 : #ifdef HAVE_POPEN
1757 5535 : stream *saveFD;
1758 :
1759 5535 : start_pager(&saveFD);
1760 : #endif
1761 :
1762 5535 : setWidth();
1763 :
1764 5535 : timerHumanCalled = false;
1765 :
1766 5537 : do {
1767 : // get the timings as reported by the backend
1768 5537 : sqloptimizer = mapi_get_sqloptimizertime(hdl);
1769 5537 : maloptimizer = mapi_get_maloptimizertime(hdl);
1770 5537 : querytime = mapi_get_querytime(hdl);
1771 5537 : timerHumanStop();
1772 : /* handle errors first */
1773 5537 : if (mapi_result_error(hdl) != NULL) {
1774 59 : mnstr_flush(toConsole, MNSTR_FLUSH_DATA);
1775 59 : if (formatter == TABLEformatter) {
1776 0 : mapi_noexplain(mid, "");
1777 : } else {
1778 59 : mapi_noexplain(mid, NULL);
1779 : }
1780 59 : mapi_explain_result(hdl, stderr);
1781 59 : errseen = true;
1782 : /* don't need to print something like '0
1783 : * tuples' if we got an error */
1784 59 : timerHuman(sqloptimizer, maloptimizer, querytime, singleinstr, false);
1785 59 : continue;
1786 : }
1787 :
1788 5478 : switch (mapi_get_querytype(hdl)) {
1789 667 : case Q_BLOCK:
1790 : case Q_PARSE:
1791 : /* should never see these */
1792 667 : continue;
1793 223 : case Q_UPDATE:
1794 223 : SQLqueryEcho(hdl);
1795 223 : if (formatter == RAWformatter ||
1796 : formatter == TESTformatter) {
1797 94 : mnstr_printf(toConsole, "[ %" PRId64 "\t]\n", mapi_rows_affected(hdl));
1798 129 : } else if (formatter != TRASHformatter && formatter != CSVformatter) {
1799 0 : aff = mapi_rows_affected(hdl);
1800 0 : lid = mapi_get_last_id(hdl);
1801 0 : mnstr_printf(toConsole,
1802 : "%" PRId64 " affected row%s",
1803 : aff,
1804 : aff != 1 ? "s" : "");
1805 0 : if (lid != -1) {
1806 0 : mnstr_printf(toConsole,
1807 : ", last generated key: "
1808 : "%" PRId64,
1809 : lid);
1810 : }
1811 0 : mnstr_printf(toConsole, "\n");
1812 : }
1813 223 : timerHuman(sqloptimizer, maloptimizer, querytime, singleinstr, false);
1814 223 : continue;
1815 533 : case Q_SCHEMA:
1816 533 : SQLqueryEcho(hdl);
1817 533 : if (formatter == TABLEformatter ||
1818 : formatter == ROWCOUNTformatter) {
1819 2 : mnstr_printf(toConsole, "operation successful\n");
1820 : }
1821 533 : timerHuman(sqloptimizer, maloptimizer, querytime, singleinstr, false);
1822 533 : continue;
1823 164 : case Q_TRANS:
1824 164 : SQLqueryEcho(hdl);
1825 164 : if (formatter == TABLEformatter ||
1826 : formatter == ROWCOUNTformatter)
1827 0 : mnstr_printf(toConsole, "auto commit mode: %s\n", mapi_get_autocommit(mid) ? "on" : "off");
1828 164 : timerHuman(sqloptimizer, maloptimizer, querytime, singleinstr, false);
1829 164 : continue;
1830 119 : case Q_PREPARE:
1831 119 : SQLqueryEcho(hdl);
1832 119 : if (formatter == TABLEformatter ||
1833 : formatter == ROWCOUNTformatter)
1834 0 : mnstr_printf(toConsole,
1835 : "execute prepared statement "
1836 : "using: EXEC %d(...)\n",
1837 : mapi_get_tableid(hdl));
1838 119 : timerHuman(sqloptimizer, maloptimizer, querytime, singleinstr, false);
1839 119 : break;
1840 : case Q_TABLE:
1841 : break;
1842 0 : default:
1843 0 : if ((formatter == TABLEformatter ||
1844 0 : formatter == ROWCOUNTformatter) &&
1845 0 : specials != DEBUGmodifier) {
1846 0 : int i;
1847 0 : mnstr_printf(stderr_stream,
1848 : "invalid/unknown response from server, "
1849 : "ignoring output\n");
1850 0 : for (i = 0; i < 5 && (reply = fetch_line(hdl)) != 0; i++)
1851 0 : mnstr_printf(stderr_stream, "? %s\n", reply);
1852 0 : if (i == 5 && fetch_line(hdl) != 0) {
1853 0 : mnstr_printf(stderr_stream,
1854 : "(remaining output omitted, "
1855 : "use \\fraw to examine in detail)\n");
1856 : /* skip over the
1857 : * unknown/invalid stuff,
1858 : * otherwise mapi_next_result
1859 : * call will assert in
1860 : * close_result because the
1861 : * logic there doesn't expect
1862 : * random unread garbage
1863 : * somehow */
1864 0 : while (fetch_line(hdl) != 0)
1865 : ;
1866 : }
1867 0 : continue;
1868 : }
1869 : }
1870 :
1871 : /* note: specials != NOmodifier implies mode == SQL */
1872 3891 : if (specials != NOmodifier && debugMode()) {
1873 0 : SQLdebugRendering(hdl);
1874 0 : continue;
1875 : }
1876 3891 : if (state == INTERRUPT)
1877 : break;
1878 3891 : if (debugMode())
1879 0 : RAWrenderer(hdl);
1880 : else {
1881 3891 : SQLqueryEcho(hdl);
1882 :
1883 3891 : switch (formatter) {
1884 0 : case TRASHformatter:
1885 0 : mapi_finish(hdl);
1886 0 : break;
1887 0 : case XMLformatter:
1888 0 : XMLrenderer(hdl);
1889 0 : break;
1890 88 : case CSVformatter:
1891 88 : CSVrenderer(hdl);
1892 88 : break;
1893 3792 : case TESTformatter:
1894 3792 : TESTrenderer(hdl);
1895 3792 : break;
1896 4 : case TABLEformatter:
1897 4 : switch (specials) {
1898 0 : case DEBUGmodifier:
1899 0 : SQLdebugRendering(hdl);
1900 0 : break;
1901 4 : default:
1902 4 : SQLrenderer(hdl);
1903 4 : break;
1904 : }
1905 : break;
1906 0 : case ROWCOUNTformatter:
1907 0 : rows = mapi_get_row_count(hdl);
1908 0 : mnstr_printf(toConsole,
1909 : "%" PRId64 " tuple%s\n", rows, rows != 1 ? "s" : "");
1910 0 : mapi_finish(hdl);
1911 0 : break;
1912 4 : case EXPANDEDformatter:
1913 4 : EXPANDEDrenderer(hdl);
1914 4 : break;
1915 3 : default:
1916 3 : RAWrenderer(hdl);
1917 3 : break;
1918 : }
1919 :
1920 3891 : timerHuman(sqloptimizer, maloptimizer, querytime, singleinstr, false);
1921 : }
1922 5537 : } while (state != INTERRUPT && mnstr_errnr(toConsole) == MNSTR_NO__ERROR && (rc = mapi_next_result(hdl)) == 1);
1923 : /*
1924 : * in case we called timerHuman() in the loop above with "total == false",
1925 : * call it again with "total == true" to get the total wall-clock time
1926 : * in case "singleinstr == false".
1927 : */
1928 5535 : if (timerHumanCalled)
1929 4868 : timerHuman(sqloptimizer, maloptimizer, querytime, singleinstr, true);
1930 5535 : if (mnstr_errnr(toConsole) != MNSTR_NO__ERROR) {
1931 0 : mnstr_printf(stderr_stream, "write error: %s\n", mnstr_peek_error(toConsole));
1932 0 : mnstr_clearerr(toConsole);
1933 0 : errseen = true;
1934 : }
1935 : #ifdef HAVE_POPEN
1936 5535 : end_pager(saveFD);
1937 : #endif
1938 :
1939 5535 : if (state == INTERRUPT)
1940 0 : mnstr_printf(toConsole, "\n");
1941 5535 : state = IDLING;
1942 :
1943 5535 : return rc;
1944 : }
1945 :
1946 : static bool
1947 16 : doRequest(Mapi mid, const char *buf)
1948 : {
1949 16 : MapiHdl hdl;
1950 :
1951 16 : if (mode == SQL)
1952 16 : SQLsetSpecial(buf);
1953 :
1954 16 : hdl = mapi_query(mid, buf);
1955 16 : if (hdl == NULL) {
1956 0 : if (formatter == TABLEformatter) {
1957 0 : mapi_noexplain(mid, "");
1958 : } else {
1959 0 : mapi_noexplain(mid, NULL);
1960 : }
1961 0 : mapi_explain(mid, stderr);
1962 0 : errseen = true;
1963 0 : return true;
1964 : }
1965 :
1966 16 : if (mapi_needmore(hdl) == MMORE)
1967 : return false;
1968 :
1969 16 : format_result(mid, hdl, false);
1970 :
1971 16 : if (mapi_get_active(mid) == NULL)
1972 16 : mapi_close_handle(hdl);
1973 16 : return errseen;
1974 : }
1975 :
1976 : #define CHECK_RESULT(mid, hdl, buf, fp) \
1977 : switch (mapi_error(mid)) { \
1978 : case MOK: /* everything A OK */ \
1979 : break; \
1980 : case MERROR: /* some error, but try to continue */ \
1981 : case MTIMEOUT: /* lost contact with the server */ \
1982 : if (formatter == TABLEformatter) { \
1983 : mapi_noexplain(mid, ""); \
1984 : } else { \
1985 : mapi_noexplain(mid, NULL); \
1986 : } \
1987 : if (hdl) { \
1988 : mapi_explain_query(hdl, stderr); \
1989 : mapi_close_handle(hdl); \
1990 : hdl = NULL; \
1991 : } else \
1992 : mapi_explain(mid, stderr); \
1993 : errseen = true; \
1994 : if (mapi_error(mid) == MERROR) \
1995 : continue; /* why not in do-while */ \
1996 : timerEnd(); \
1997 : if (buf) \
1998 : free(buf); \
1999 : if (fp) \
2000 : close_stream(fp); \
2001 : return 1; \
2002 : }
2003 :
2004 : static bool
2005 0 : doFileBulk(Mapi mid, stream *fp)
2006 : {
2007 0 : char *buf = NULL;
2008 0 : size_t semicolon1 = 0, semicolon2 = 0;
2009 0 : ssize_t length;
2010 0 : MapiHdl hdl = mapi_get_active(mid);
2011 0 : MapiMsg rc = MOK;
2012 0 : size_t bufsize = 0;
2013 :
2014 0 : bufsize = 10240;
2015 0 : buf = malloc(bufsize + 1);
2016 0 : if (!buf) {
2017 0 : mnstr_printf(stderr_stream, "cannot allocate memory for send buffer\n");
2018 0 : if (fp)
2019 0 : close_stream(fp);
2020 0 : return true;
2021 : }
2022 :
2023 0 : timerStart();
2024 0 : do {
2025 0 : timerPause();
2026 0 : if (fp == NULL) {
2027 0 : if (hdl == NULL)
2028 : break;
2029 0 : length = 0;
2030 0 : buf[0] = 0;
2031 : } else {
2032 0 : while ((length = mnstr_read(fp, buf, 1, bufsize)) < 0) {
2033 0 : if (mnstr_errnr(fp) == MNSTR_INTERRUPT)
2034 0 : continue;
2035 : /* error */
2036 0 : errseen = true;
2037 0 : break;
2038 : }
2039 0 : if (length < 0)
2040 : break; /* nothing more to do */
2041 0 : buf[length] = 0;
2042 0 : if (length == 0) {
2043 : /* end of file */
2044 0 : if (semicolon2 == 0 && hdl == NULL)
2045 : break; /* nothing more to do */
2046 : } else {
2047 0 : if (strlen(buf) < (size_t) length) {
2048 0 : mnstr_printf(stderr_stream, "NULL byte in input\n");
2049 0 : errseen = true;
2050 0 : break;
2051 : }
2052 0 : while (length > 1 && buf[length - 1] == ';') {
2053 0 : semicolon1++;
2054 0 : buf[--length] = 0;
2055 : }
2056 : }
2057 : }
2058 0 : timerResume();
2059 0 : if (hdl == NULL) {
2060 0 : hdl = mapi_query_prep(mid);
2061 0 : CHECK_RESULT(mid, hdl, buf, fp);
2062 : }
2063 :
2064 0 : assert(hdl != NULL);
2065 0 : while (semicolon2 > 0) {
2066 0 : mapi_query_part(hdl, ";", 1);
2067 0 : CHECK_RESULT(mid, hdl, buf, fp);
2068 0 : semicolon2--;
2069 : }
2070 0 : semicolon2 = semicolon1;
2071 0 : semicolon1 = 0;
2072 0 : if (length > 0)
2073 0 : mapi_query_part(hdl, buf, (size_t) length);
2074 0 : CHECK_RESULT(mid, hdl, buf, fp);
2075 :
2076 : /* if not at EOF, make sure there is a newline in the
2077 : * buffer */
2078 0 : if (length > 0 && strchr(buf, '\n') == NULL)
2079 0 : continue;
2080 :
2081 0 : assert(hdl != NULL);
2082 : /* If the server wants more but we're at the end of
2083 : * file (length == 0), notify the server that we
2084 : * don't have anything more. If the server still
2085 : * wants more (shouldn't happen according to the
2086 : * protocol) we break out of the loop (via the
2087 : * continue). The assertion at the end will then go
2088 : * off. */
2089 0 : if (mapi_query_done(hdl) == MMORE &&
2090 0 : (length > 0 || mapi_query_done(hdl) == MMORE))
2091 0 : continue; /* get more data */
2092 :
2093 0 : CHECK_RESULT(mid, hdl, buf, fp);
2094 :
2095 0 : rc = format_result(mid, hdl, false);
2096 :
2097 0 : if (rc == MMORE && (length > 0 || mapi_query_done(hdl) != MOK))
2098 0 : continue; /* get more data */
2099 :
2100 0 : CHECK_RESULT(mid, hdl, buf, fp);
2101 :
2102 0 : mapi_close_handle(hdl);
2103 0 : hdl = NULL;
2104 :
2105 0 : } while (length > 0);
2106 : /* reached on end of file */
2107 0 : if (hdl)
2108 0 : mapi_close_handle(hdl);
2109 0 : timerEnd();
2110 :
2111 0 : free(buf);
2112 0 : mnstr_flush(toConsole, MNSTR_FLUSH_DATA);
2113 0 : if (fp)
2114 0 : close_stream(fp);
2115 0 : return errseen;
2116 : }
2117 :
2118 : /* The options available for controlling input and rendering depends
2119 : * on the language mode. */
2120 :
2121 : static void
2122 0 : showCommands(void)
2123 : {
2124 : /* shared control options */
2125 0 : mnstr_printf(toConsole, "\\? - show this message\n");
2126 0 : if (mode == MAL)
2127 0 : mnstr_printf(toConsole, "?pat - MAL function help. pat=[modnme[.fcnnme][(][)]] wildcard *\n");
2128 0 : mnstr_printf(toConsole, "\\<file - read input from file\n"
2129 : "\\>file - save response in file, or stdout if no file is given\n");
2130 : #ifdef HAVE_POPEN
2131 0 : mnstr_printf(toConsole, "\\|cmd - pipe result to process, or stop when no command is given\n");
2132 : #endif
2133 : #ifdef HAVE_LIBREADLINE
2134 0 : mnstr_printf(toConsole, "\\history - show the readline history\n");
2135 : #endif
2136 0 : if (mode == SQL) {
2137 0 : mnstr_printf(toConsole, "\\help - synopsis of the SQL syntax\n"
2138 : "\\D table - dumps the table, or the complete database if none given.\n"
2139 : "\\d[Stvsfn]+ [obj] - list database objects, or describe if obj given\n"
2140 : "\\A - enable auto commit\n"
2141 : "\\a - disable auto commit\n");
2142 : }
2143 0 : mnstr_printf(toConsole, "\\e - echo the query in sql formatting mode\n"
2144 : "\\t - set the timer {none,clock,performance} (none is default)\n"
2145 : "\\f - format using renderer {csv,tab,raw,sql,xml,trash,rowcount,expanded}\n"
2146 : "\\w# - set maximal page width (-1=unlimited, 0=terminal width, >0=limit to num)\n"
2147 : "\\r# - set maximum rows per page (-1=raw)\n"
2148 : "\\L file - save client-server interaction\n"
2149 : "\\X - trace mclient code\n"
2150 : "\\q - terminate session and quit mclient\n");
2151 0 : }
2152 :
2153 : #define MD_TABLE 1
2154 : #define MD_VIEW 2
2155 : #define MD_SEQ 4
2156 : #define MD_FUNC 8
2157 : #define MD_SCHEMA 16
2158 :
2159 : #define READBLOCK 8192
2160 :
2161 : #ifdef HAVE_LIBREADLINE
2162 : struct myread_t {
2163 : stream *s;
2164 : const char *prompt;
2165 : char *buf;
2166 : size_t read;
2167 : size_t len;
2168 : };
2169 :
2170 : static ssize_t
2171 0 : myread(void *restrict private, void *restrict buf, size_t elmsize, size_t cnt)
2172 : {
2173 0 : struct myread_t *p = private;
2174 0 : size_t size = elmsize * cnt;
2175 0 : size_t cpsize = size;
2176 :
2177 0 : assert(elmsize == 1);
2178 0 : if (size == 0)
2179 0 : return cnt;
2180 0 : if (p->buf == NULL) {
2181 0 : rl_completion_func_t *func = NULL;
2182 :
2183 0 : if (strcmp(p->prompt, "more>") == 0) {
2184 0 : func = suspend_completion();
2185 : }
2186 0 : p->buf = call_readline(p->prompt);
2187 0 : if (func)
2188 0 : continue_completion(func);
2189 0 : if (p->buf == (char *) -1) {
2190 0 : p->buf = NULL;
2191 0 : return -1;
2192 : }
2193 0 : if (p->buf == NULL)
2194 : return 0;
2195 0 : p->len = strlen(p->buf);
2196 0 : p->read = 0;
2197 0 : if (p->len > 1)
2198 0 : save_line(p->buf);
2199 : }
2200 0 : if (p->read < p->len) {
2201 0 : if (p->len - p->read < size)
2202 : cpsize = p->len - p->read;
2203 0 : memcpy(buf, p->buf + p->read, cpsize);
2204 0 : p->read += cpsize;
2205 : } else {
2206 : cpsize = 0;
2207 : }
2208 0 : if (p->read == p->len && cpsize < size) {
2209 0 : ((char *) buf)[cpsize++] = '\n';
2210 0 : free(p->buf);
2211 0 : p->buf = NULL;
2212 : }
2213 0 : return cpsize / elmsize;
2214 : }
2215 :
2216 : static void
2217 0 : mydestroy(void *private)
2218 : {
2219 0 : struct myread_t *p = private;
2220 :
2221 0 : if (p->buf)
2222 0 : free(p->buf);
2223 0 : }
2224 : #endif
2225 :
2226 : static bool
2227 137 : doFile(Mapi mid, stream *fp, bool useinserts, bool interactive, bool save_history)
2228 : {
2229 137 : char *line = NULL;
2230 137 : char *buf = NULL;
2231 137 : size_t length;
2232 137 : size_t bufsiz = 0;
2233 137 : MapiHdl hdl;
2234 137 : MapiMsg rc = MOK;
2235 137 : int lineno = 1;
2236 137 : char *prompt = NULL;
2237 137 : int prepno = 0;
2238 : #ifdef HAVE_LIBREADLINE
2239 137 : struct myread_t rl;
2240 : #endif
2241 137 : int fd;
2242 :
2243 137 : (void) save_history; /* not used if no readline */
2244 137 : if ((fd = getFileNo(fp)) >= 0 && isatty(fd)
2245 : #ifdef WIN32 /* isatty may not give expected result */
2246 : && formatter != TESTformatter
2247 : #endif
2248 : ) {
2249 0 : interactive = true;
2250 0 : setPrompt();
2251 0 : prompt = promptbuf;
2252 0 : fromConsole = fp;
2253 : #ifdef HAVE_LIBREADLINE
2254 0 : init_readline(mid, language, save_history);
2255 0 : rl.s = fp;
2256 0 : rl.buf = NULL;
2257 0 : if ((fp = callback_stream(&rl, myread, NULL, NULL, mydestroy, mnstr_name(fp))) == NULL) {
2258 0 : mnstr_printf(stderr_stream,"Malloc for doFile failed");
2259 0 : exit(2);
2260 : }
2261 : #endif
2262 : }
2263 : #ifdef HAVE_ICONV
2264 137 : if (encoding) {
2265 0 : if ((fp = iconv_rstream(fp, encoding, mnstr_name(fp))) == NULL) {
2266 0 : mnstr_printf(stderr_stream,"Malloc failure");
2267 0 : exit(2);
2268 : }
2269 : }
2270 : #endif
2271 :
2272 137 : if (!interactive && !echoquery)
2273 0 : return doFileBulk(mid, fp);
2274 :
2275 137 : hdl = mapi_get_active(mid);
2276 :
2277 137 : bufsiz = READBLOCK;
2278 137 : buf = malloc(bufsiz);
2279 137 : if (buf == NULL) {
2280 0 : mnstr_printf(stderr_stream,"Malloc for doFile failed");
2281 0 : exit(2);
2282 : }
2283 :
2284 108986 : do {
2285 108986 : bool seen_null_byte;
2286 108986 : repeat:
2287 108986 : seen_null_byte = false;
2288 :
2289 108986 : if (prompt) {
2290 0 : char *p = hdl ? "more>" : prompt;
2291 : /* clear errors when interactive */
2292 0 : errseen = false;
2293 : #ifdef HAVE_LIBREADLINE
2294 0 : rl.prompt = p;
2295 : #else
2296 : mnstr_write(toConsole, p, 1, strlen(p));
2297 : #endif
2298 : }
2299 108986 : mnstr_flush(toConsole, MNSTR_FLUSH_DATA);
2300 108986 : timerPause();
2301 : /* read a line */
2302 108986 : length = 0;
2303 109258 : for (;;) {
2304 109258 : ssize_t l;
2305 109258 : char *newbuf;
2306 109258 : state = READING;
2307 109258 : l = mnstr_readline(fp, buf + length, bufsiz - length);
2308 109258 : if (l <= 0 && state == INTERRUPT) {
2309 : /* we were interrupted */
2310 0 : mnstr_clearerr(fp);
2311 0 : mnstr_write(toConsole, "\n", 1, 1);
2312 0 : if (hdl) {
2313 : /* on interrupt when continuing a query, force an error */
2314 0 : l = 0;
2315 0 : if (mapi_query_abort(hdl, 1) != MOK) {
2316 : /* if abort failed, insert something not allowed */
2317 0 : buf[l++] = '\200';
2318 : }
2319 0 : buf[l++] = '\n';
2320 0 : length = 0;
2321 : } else {
2322 : /* not continuing; just repeat */
2323 0 : goto repeat;
2324 : }
2325 : }
2326 109258 : state = IDLING;
2327 109258 : if (l <= 0)
2328 : break;
2329 109103 : if (!seen_null_byte && strlen(buf + length) < (size_t) l) {
2330 1 : mnstr_printf(stderr_stream, "NULL byte in input on line %d of input\n", lineno);
2331 1 : seen_null_byte = true;
2332 1 : errseen = true;
2333 1 : if (hdl) {
2334 0 : mapi_close_handle(hdl);
2335 0 : hdl = NULL;
2336 : }
2337 : }
2338 109103 : length += l;
2339 109103 : if (buf[length - 1] == '\n')
2340 : break;
2341 272 : newbuf = realloc(buf, bufsiz += READBLOCK);
2342 272 : if (newbuf) {
2343 : buf = newbuf;
2344 : } else {
2345 0 : mnstr_printf(stderr_stream,"Malloc failure");
2346 0 : length = 0;
2347 0 : errseen = true;
2348 0 : if (hdl) {
2349 0 : mapi_close_handle(hdl);
2350 0 : hdl = NULL;
2351 : }
2352 : break;
2353 : }
2354 : }
2355 108986 : line = buf;
2356 108986 : lineno++;
2357 108986 : if (seen_null_byte)
2358 1 : continue;
2359 108985 : if (length == 0) {
2360 : /* end of file */
2361 144 : if (hdl == NULL) {
2362 : /* nothing more to do */
2363 137 : goto bailout;
2364 : }
2365 :
2366 : /* hdl != NULL, we should finish the current query */
2367 : }
2368 108848 : if (hdl == NULL && length > 0 && interactive) {
2369 : /* test for special commands */
2370 5520 : if (mode != MAL)
2371 5868 : while (length > 0 &&
2372 5531 : (*line == '\f' ||
2373 : *line == '\n' ||
2374 : *line == ' ')) {
2375 348 : line++;
2376 348 : length--;
2377 : }
2378 : /* in the switch, use continue if the line was
2379 : * processed, use break to send to server */
2380 5520 : switch (*line) {
2381 : case '\n':
2382 : case '\0':
2383 : break;
2384 89 : case 'e':
2385 : case 'E':
2386 : /* a bit of a hack for prepare/exec/deallocate
2387 : * tests: replace "exec[ute] **" with the
2388 : * ID of the last prepared statement */
2389 89 : if (mode == SQL && formatter == TESTformatter) {
2390 89 : if (strncasecmp(line, "exec **", 7) == 0) {
2391 84 : line[5] = prepno < 10 ? ' ' : prepno / 10 + '0';
2392 84 : line[6] = prepno % 10 + '0';
2393 5 : } else if (strncasecmp(line, "execute **", 10) == 0) {
2394 2 : line[8] = prepno < 10 ? ' ' : prepno / 10 + '0';
2395 2 : line[9] = prepno % 10 + '0';
2396 : }
2397 : }
2398 89 : if (strncasecmp(line, "exit\n", 5) == 0) {
2399 0 : goto bailout;
2400 : }
2401 : break;
2402 44 : case 'd':
2403 : case 'D':
2404 : /* a bit of a hack for prepare/exec/deallocate
2405 : * tests: replace "deallocate **" with the
2406 : * ID of the last prepared statement */
2407 44 : if (mode == SQL && formatter == TESTformatter && strncasecmp(line, "deallocate **", 13) == 0) {
2408 3 : line[11] = prepno < 10 ? ' ' : prepno / 10 + '0';
2409 3 : line[12] = prepno % 10 + '0';
2410 : }
2411 : break;
2412 0 : case 'q':
2413 : case 'Q':
2414 0 : if (strncasecmp(line, "quit\n", 5) == 0) {
2415 0 : goto bailout;
2416 : }
2417 : break;
2418 2 : case '\\':
2419 2 : switch (line[1]) {
2420 0 : case 'q':
2421 0 : goto bailout;
2422 0 : case 'X':
2423 : /* toggle interaction trace */
2424 0 : mapi_trace(mid, !mapi_get_trace(mid));
2425 0 : continue;
2426 0 : case 'A':
2427 0 : if (mode != SQL)
2428 : break;
2429 0 : mapi_setAutocommit(mid, true);
2430 0 : continue;
2431 0 : case 'a':
2432 0 : if (mode != SQL)
2433 : break;
2434 0 : mapi_setAutocommit(mid, false);
2435 0 : continue;
2436 0 : case 'w':
2437 0 : pagewidth = atoi(line + 2);
2438 0 : pagewidthset = pagewidth != 0;
2439 0 : continue;
2440 0 : case 'r':
2441 0 : rowsperpage = atoi(line + 2);
2442 0 : continue;
2443 0 : case 'd': {
2444 0 : bool hasWildcard = false;
2445 0 : bool hasSchema = false;
2446 0 : bool wantsSystem = false;
2447 0 : unsigned int x = 0;
2448 0 : char *p, *q;
2449 0 : bool escaped = false;
2450 0 : if (mode != SQL)
2451 : break;
2452 0 : while (my_isspace(line[length - 1]))
2453 0 : line[--length] = 0;
2454 0 : for (line += 2;
2455 0 : *line && !my_isspace(*line);
2456 0 : line++) {
2457 0 : switch (*line) {
2458 0 : case 't':
2459 0 : x |= MD_TABLE;
2460 0 : break;
2461 0 : case 'v':
2462 0 : x |= MD_VIEW;
2463 0 : break;
2464 0 : case 's':
2465 0 : x |= MD_SEQ;
2466 0 : break;
2467 0 : case 'f':
2468 0 : x |= MD_FUNC;
2469 0 : break;
2470 0 : case 'n':
2471 0 : x |= MD_SCHEMA;
2472 0 : break;
2473 : case 'S':
2474 : wantsSystem = true;
2475 : break;
2476 0 : default:
2477 0 : mnstr_printf(stderr_stream, "unknown sub-command for \\d: %c\n", *line);
2478 0 : length = 0;
2479 0 : line[1] = '\0';
2480 0 : break;
2481 : }
2482 : }
2483 0 : if (length == 0)
2484 0 : continue;
2485 0 : if (x == 0) /* default to tables and views */
2486 0 : x = MD_TABLE | MD_VIEW;
2487 0 : for ( ; *line && my_isspace(*line); line++)
2488 : ;
2489 :
2490 : /* lowercase the object, except for quoted parts */
2491 : q = line;
2492 0 : for (p = line; *p != '\0'; p++) {
2493 0 : if (*p == '"') {
2494 0 : if (escaped) {
2495 0 : if (*(p + 1) == '"') {
2496 : /* SQL escape */
2497 0 : *q++ = *p++;
2498 : } else {
2499 : escaped = false;
2500 : }
2501 : } else {
2502 : escaped = true;
2503 : }
2504 : } else {
2505 0 : if (!escaped) {
2506 0 : *q++ = tolower((int) *p);
2507 0 : if (*p == '*') {
2508 0 : *p = '%';
2509 0 : hasWildcard = true;
2510 0 : } else if (*p == '?') {
2511 0 : *p = '_';
2512 0 : hasWildcard = true;
2513 0 : } else if (*p == '.') {
2514 0 : hasSchema = true;
2515 : }
2516 : } else {
2517 0 : *q++ = *p;
2518 : }
2519 : }
2520 : }
2521 0 : *q = '\0';
2522 0 : if (escaped) {
2523 0 : mnstr_printf(stderr_stream, "unexpected end of string while "
2524 : "looking for matching \"\n");
2525 0 : continue;
2526 : }
2527 :
2528 0 : if (*line && !hasWildcard) {
2529 : #ifdef HAVE_POPEN
2530 0 : stream *saveFD;
2531 :
2532 0 : start_pager(&saveFD);
2533 : #endif
2534 0 : if (x & (MD_TABLE | MD_VIEW))
2535 0 : dump_table(mid, NULL, line, toConsole, NULL, NULL, true, true, false, false, false, false);
2536 0 : if (x & MD_SEQ)
2537 0 : describe_sequence(mid, NULL, line, toConsole);
2538 0 : if (x & MD_FUNC)
2539 0 : dump_functions(mid, toConsole, 0, NULL, line, NULL);
2540 0 : if (x & MD_SCHEMA)
2541 0 : describe_schema(mid, line, toConsole);
2542 : #ifdef HAVE_POPEN
2543 0 : end_pager(saveFD);
2544 : #endif
2545 : } else {
2546 : /* get all object names in current schema */
2547 0 : const char *with_clause =
2548 : "with describe_all_objects AS (\n"
2549 : " SELECT s.name AS sname,\n"
2550 : " t.name,\n"
2551 : " s.name || '.' || t.name AS fullname,\n"
2552 : " CAST(CASE t.type\n"
2553 : " WHEN 1 THEN 2\n" /* ntype for views */
2554 : " ELSE 1\n" /* ntype for tables */
2555 : " END AS SMALLINT) AS ntype,\n"
2556 : " (CASE WHEN t.system THEN 'SYSTEM ' ELSE '' END) || tt.table_type_name AS type,\n"
2557 : " t.system,\n"
2558 : " c.remark AS remark\n"
2559 : " FROM sys._tables t\n"
2560 : " LEFT OUTER JOIN sys.comments c ON t.id = c.id\n"
2561 : " LEFT OUTER JOIN sys.schemas s ON t.schema_id = s.id\n"
2562 : " LEFT OUTER JOIN sys.table_types tt ON t.type = tt.table_type_id\n"
2563 : " UNION ALL\n"
2564 : " SELECT s.name AS sname,\n"
2565 : " sq.name,\n"
2566 : " s.name || '.' || sq.name AS fullname,\n"
2567 : " CAST(4 AS SMALLINT) AS ntype,\n"
2568 : " 'SEQUENCE' AS type,\n"
2569 : " false AS system,\n"
2570 : " c.remark AS remark\n"
2571 : " FROM sys.sequences sq\n"
2572 : " LEFT OUTER JOIN sys.comments c ON sq.id = c.id\n"
2573 : " LEFT OUTER JOIN sys.schemas s ON sq.schema_id = s.id\n"
2574 : " UNION ALL\n"
2575 : " SELECT DISTINCT s.name AS sname,\n" /* DISTINCT is needed to filter out duplicate overloaded function/procedure names */
2576 : " f.name,\n"
2577 : " s.name || '.' || f.name AS fullname,\n"
2578 : " CAST(8 AS SMALLINT) AS ntype,\n"
2579 : " (CASE WHEN f.system THEN 'SYSTEM ' ELSE '' END) || function_type_keyword AS type,\n"
2580 : " f.system AS system,\n"
2581 : " c.remark AS remark\n"
2582 : " FROM sys.functions f\n"
2583 : " LEFT OUTER JOIN sys.comments c ON f.id = c.id\n"
2584 : " LEFT OUTER JOIN sys.function_types ft ON f.type = ft.function_type_id\n"
2585 : " LEFT OUTER JOIN sys.schemas s ON f.schema_id = s.id\n"
2586 : " UNION ALL\n"
2587 : " SELECT NULL AS sname,\n"
2588 : " s.name,\n"
2589 : " s.name AS fullname,\n"
2590 : " CAST(16 AS SMALLINT) AS ntype,\n"
2591 : " (CASE WHEN s.system THEN 'SYSTEM SCHEMA' ELSE 'SCHEMA' END) AS type,\n"
2592 : " s.system,\n"
2593 : " c.remark AS remark\n"
2594 : " FROM sys.schemas s\n"
2595 : " LEFT OUTER JOIN sys.comments c ON s.id = c.id\n"
2596 : " ORDER BY system, name, sname, ntype)\n"
2597 : ;
2598 0 : size_t len = strlen(with_clause) + 400 + strlen(line);
2599 0 : char *query = malloc(len);
2600 0 : char *q = query, *endq = query + len;
2601 :
2602 0 : if (query == NULL) {
2603 0 : mnstr_printf(stderr_stream, "memory allocation failure\n");
2604 0 : continue;
2605 : }
2606 :
2607 : /*
2608 : * | LINE | SCHEMA FILTER | NAME FILTER |
2609 : * |-----------------+---------------+-------------------------------|
2610 : * | "" | yes | - |
2611 : * | "my_table" | yes | name LIKE 'my_table' |
2612 : * | "my*" | yes | name LIKE 'my%' |
2613 : * | "data.my_table" | no | fullname LIKE 'data.my_table' |
2614 : * | "data.my*" | no | fullname LIKE 'data.my%' |
2615 : * | "*a.my*" | no | fullname LIKE '%a.my%' |
2616 : */
2617 0 : q += snprintf(q, endq - q, "%s", with_clause);
2618 0 : q += snprintf(q, endq - q, " SELECT type, fullname, remark FROM describe_all_objects WHERE (ntype & %u) > 0", x);
2619 0 : if (!wantsSystem) {
2620 0 : q += snprintf(q, endq - q, " AND NOT system");
2621 : }
2622 0 : if (!hasSchema) {
2623 0 : q += snprintf(q, endq - q, " AND (sname IS NULL OR sname = current_schema)");
2624 : }
2625 0 : if (*line) {
2626 0 : q += snprintf(q, endq - q, " AND (%s LIKE '%s')", (hasSchema ? "fullname" : "name"), line);
2627 : }
2628 0 : q += snprintf(q, endq - q, " ORDER BY fullname, type, remark");
2629 :
2630 : #ifdef HAVE_POPEN
2631 0 : stream *saveFD;
2632 0 : start_pager(&saveFD);
2633 : #endif
2634 :
2635 0 : hdl = mapi_query(mid, query);
2636 0 : free(query);
2637 0 : CHECK_RESULT(mid, hdl, buf, fp);
2638 0 : while (fetch_row(hdl) == 3) {
2639 0 : char *type = mapi_fetch_field(hdl, 0);
2640 0 : char *name = mapi_fetch_field(hdl, 1);
2641 0 : char *remark = mapi_fetch_field(hdl, 2);
2642 0 : int type_width = mapi_get_len(hdl, 0);
2643 0 : int name_width = mapi_get_len(hdl, 1);
2644 0 : mnstr_printf(toConsole,
2645 : "%-*s %-*s",
2646 : type_width, type,
2647 0 : name_width * (remark != NULL), name);
2648 0 : if (remark) {
2649 0 : char *c;
2650 0 : mnstr_printf(toConsole, " '");
2651 0 : for (c = remark; *c; c++) {
2652 0 : switch (*c) {
2653 0 : case '\'':
2654 0 : mnstr_printf(toConsole, "''");
2655 0 : break;
2656 0 : default:
2657 0 : mnstr_writeChr(toConsole, *c);
2658 : }
2659 : }
2660 0 : mnstr_printf(toConsole, "'");
2661 : }
2662 0 : mnstr_printf(toConsole, "\n");
2663 :
2664 : }
2665 0 : mapi_close_handle(hdl);
2666 0 : hdl = NULL;
2667 : #ifdef HAVE_POPEN
2668 0 : end_pager(saveFD);
2669 : #endif
2670 : }
2671 0 : continue;
2672 : }
2673 0 : case 'D':{
2674 : #ifdef HAVE_POPEN
2675 0 : stream *saveFD;
2676 : #endif
2677 :
2678 0 : if (mode != SQL)
2679 : break;
2680 0 : while (my_isspace(line[length - 1]))
2681 0 : line[--length] = 0;
2682 0 : if (line[2] && !my_isspace(line[2])) {
2683 0 : mnstr_printf(stderr_stream, "space required after \\D\n");
2684 0 : continue;
2685 : }
2686 0 : for (line += 2; *line && my_isspace(*line); line++)
2687 : ;
2688 : #ifdef HAVE_POPEN
2689 0 : start_pager(&saveFD);
2690 : #endif
2691 0 : if (*line) {
2692 0 : mnstr_printf(toConsole, "START TRANSACTION;\n");
2693 0 : dump_table(mid, NULL, line, toConsole, NULL, NULL, false, true, useinserts, false, false, false);
2694 0 : mnstr_printf(toConsole, "COMMIT;\n");
2695 : } else
2696 0 : dump_database(mid, toConsole, NULL, NULL, false, useinserts, false);
2697 : #ifdef HAVE_POPEN
2698 0 : end_pager(saveFD);
2699 : #endif
2700 0 : continue;
2701 : }
2702 0 : case '<': {
2703 : stream *s;
2704 : /* read commands from file */
2705 0 : while (my_isspace(line[length - 1]))
2706 0 : line[--length] = 0;
2707 0 : for (line += 2; *line && my_isspace(*line); line++)
2708 : ;
2709 : /* use open_rastream to
2710 : * convert filename from UTF-8
2711 : * to locale */
2712 0 : if ((s = open_rastream(line)) == NULL ||
2713 0 : mnstr_errnr(s) != MNSTR_NO__ERROR) {
2714 0 : if (s)
2715 0 : close_stream(s);
2716 0 : mnstr_printf(stderr_stream, "Cannot open %s: %s\n", line, mnstr_peek_error(NULL));
2717 : } else {
2718 0 : const char *oldfile = curfile;
2719 0 : char *newfile = strdup(line);
2720 0 : curfile = newfile;
2721 0 : doFile(mid, s, 0, 0, 0);
2722 0 : curfile = oldfile;
2723 0 : free(newfile);
2724 : }
2725 0 : continue;
2726 : }
2727 : case '>':
2728 : /* redirect output to file */
2729 2 : while (my_isspace(line[length - 1]))
2730 1 : line[--length] = 0;
2731 1 : for (line += 2; *line && my_isspace(*line); line++)
2732 : ;
2733 1 : if (toConsole != stdout_stream &&
2734 0 : toConsole != stderr_stream) {
2735 0 : close_stream(toConsole);
2736 : }
2737 1 : if (*line == 0 ||
2738 1 : strcmp(line, "stdout") == 0)
2739 0 : toConsole = stdout_stream;
2740 1 : else if (strcmp(line, "stderr") == 0)
2741 0 : toConsole = stderr_stream;
2742 2 : else if ((toConsole = open_wastream(line)) == NULL ||
2743 1 : mnstr_errnr(toConsole) != MNSTR_NO__ERROR) {
2744 0 : mnstr_printf(stderr_stream, "Cannot open %s: %s\n", line, mnstr_peek_error(toConsole));
2745 0 : if (toConsole != NULL) {
2746 0 : close_stream(toConsole);
2747 : }
2748 0 : toConsole = stdout_stream;
2749 : }
2750 1 : continue;
2751 0 : case 'L':
2752 0 : free(logfile);
2753 0 : logfile = NULL;
2754 0 : while (my_isspace(line[length - 1]))
2755 0 : line[--length] = 0;
2756 0 : for (line += 2; *line && my_isspace(*line); line++)
2757 : ;
2758 0 : if (*line == 0) {
2759 : /* turn of logging */
2760 0 : mapi_log(mid, NULL);
2761 : } else {
2762 0 : logfile = strdup(line);
2763 0 : mapi_log(mid, logfile);
2764 : }
2765 0 : continue;
2766 0 : case '?':
2767 0 : showCommands();
2768 0 : continue;
2769 : #ifdef HAVE_POPEN
2770 0 : case '|':
2771 0 : free(pager);
2772 0 : pager = NULL;
2773 0 : setWidth(); /* reset to system default */
2774 :
2775 0 : while (my_isspace(line[length - 1]))
2776 0 : line[--length] = 0;
2777 0 : for (line += 2; *line && my_isspace(*line); line++)
2778 : ;
2779 0 : if (*line == 0)
2780 0 : continue;
2781 0 : pager = strdup(line);
2782 0 : continue;
2783 : #endif
2784 0 : case 'h':
2785 0 : {
2786 : #ifdef HAVE_LIBREADLINE
2787 0 : int h;
2788 0 : char *nl;
2789 :
2790 0 : if (strcmp(line,"\\history\n") == 0) {
2791 0 : for (h = 0; h < history_length; h++) {
2792 0 : nl = history_get(h) ? history_get(h)->line : 0;
2793 0 : if (nl)
2794 0 : mnstr_printf(toConsole, "%d %s\n", h, nl);
2795 : }
2796 : } else
2797 : #endif
2798 : {
2799 0 : setWidth();
2800 0 : sql_help(line, toConsole, pagewidth <= 0 ? DEFWIDTH : pagewidth);
2801 : }
2802 0 : continue;
2803 : }
2804 : #if 0 /* for later */
2805 : #ifdef HAVE_LIBREADLINE
2806 : case '!':
2807 : {
2808 : char *nl;
2809 :
2810 : nl = strchr(line, '\n');
2811 : if (nl)
2812 : *nl = 0;
2813 : if (history_expand(line + 2, &nl)) {
2814 : mnstr_printf(toConsole, "%s\n", nl);
2815 : }
2816 : mnstr_printf(toConsole, "Expansion needs work\n");
2817 : continue;
2818 : }
2819 : #endif
2820 : #endif /* 0 */
2821 0 : case 'e':
2822 0 : echoquery = true;
2823 0 : continue;
2824 : case 'f':
2825 2 : while (my_isspace(line[length - 1]))
2826 1 : line[--length] = 0;
2827 2 : for (line += 2; *line && my_isspace(*line); line++)
2828 : ;
2829 1 : if (*line == 0) {
2830 0 : mnstr_printf(toConsole, "Current formatter: ");
2831 0 : switch (formatter) {
2832 0 : case RAWformatter:
2833 0 : mnstr_printf(toConsole, "raw\n");
2834 0 : break;
2835 0 : case TABLEformatter:
2836 0 : mnstr_printf(toConsole, "sql\n");
2837 0 : break;
2838 0 : case CSVformatter:
2839 0 : mnstr_printf(toConsole, "%s\n", separator[0] == '\t' ? "tab" : "csv");
2840 0 : break;
2841 0 : case TRASHformatter:
2842 0 : mnstr_printf(toConsole, "trash\n");
2843 0 : break;
2844 0 : case ROWCOUNTformatter:
2845 0 : mnstr_printf(toConsole, "rowcount\n");
2846 0 : break;
2847 0 : case XMLformatter:
2848 0 : mnstr_printf(toConsole, "xml\n");
2849 0 : break;
2850 0 : case EXPANDEDformatter:
2851 0 : mnstr_printf(toConsole, "expanded\n");
2852 0 : break;
2853 0 : default:
2854 0 : mnstr_printf(toConsole, "none\n");
2855 0 : break;
2856 : }
2857 : } else {
2858 1 : setFormatter(line);
2859 1 : if (mode == SQL)
2860 1 : mapi_set_size_header(mid, strcmp(line, "raw") == 0);
2861 : }
2862 1 : continue;
2863 : case 't':
2864 0 : while (my_isspace(line[length - 1]))
2865 0 : line[--length] = 0;
2866 0 : for (line += 2; *line && my_isspace(*line); line++)
2867 : ;
2868 0 : if (*line == 0) {
2869 0 : mnstr_printf(toConsole, "Current time formatter: ");
2870 0 : if (timermode == T_NONE)
2871 0 : mnstr_printf(toConsole,"none\n");
2872 0 : if (timermode == T_CLOCK)
2873 0 : mnstr_printf(toConsole,"clock\n");
2874 0 : if (timermode == T_PERF)
2875 0 : mnstr_printf(toConsole,"performance\n");
2876 0 : } else if (strcmp(line,"none") == 0) {
2877 0 : timermode = T_NONE;
2878 0 : } else if (strcmp(line,"clock") == 0) {
2879 0 : timermode = T_CLOCK;
2880 0 : } else if (strncmp(line,"perf",4) == 0 || strcmp(line,"performance") == 0) {
2881 0 : timermode = T_PERF;
2882 0 : } else if (*line != '\0') {
2883 0 : mnstr_printf(stderr_stream, "warning: invalid argument to -t: %s\n",
2884 : line);
2885 : }
2886 0 : continue;
2887 0 : default:
2888 0 : showCommands();
2889 0 : continue;
2890 : }
2891 : }
2892 : }
2893 :
2894 108846 : if (hdl == NULL) {
2895 5519 : timerStart();
2896 5519 : hdl = mapi_query_prep(mid);
2897 5519 : CHECK_RESULT(mid, hdl, buf, fp);
2898 : } else
2899 103327 : timerResume();
2900 :
2901 108846 : assert(hdl != NULL);
2902 :
2903 108846 : if (length > 0) {
2904 108502 : SQLsetSpecial(line);
2905 108502 : mapi_query_part(hdl, line, length);
2906 108502 : CHECK_RESULT(mid, hdl, buf, fp);
2907 : }
2908 :
2909 : /* If the server wants more but we're at the
2910 : * end of file (line == NULL), notify the
2911 : * server that we don't have anything more.
2912 : * If the server still wants more (shouldn't
2913 : * happen according to the protocol) we break
2914 : * out of the loop (via the continue). The
2915 : * assertion at the end will then go off. */
2916 108846 : if (mapi_query_done(hdl) == MMORE) {
2917 103327 : if (line != NULL) {
2918 103327 : continue; /* get more data */
2919 0 : } else if (mapi_query_done(hdl) == MMORE) {
2920 0 : hdl = NULL;
2921 0 : continue; /* done */
2922 : }
2923 : }
2924 5519 : CHECK_RESULT(mid, hdl, buf, fp);
2925 :
2926 5519 : if (mapi_get_querytype(hdl) == Q_PREPARE) {
2927 119 : prepno = mapi_get_tableid(hdl);
2928 119 : assert(mode != SQL || formatter != TESTformatter || prepno < 100); /* prepno is used only at the TestWeb */
2929 : }
2930 :
2931 11038 : rc = format_result(mid, hdl, interactive || echoquery);
2932 :
2933 5519 : if (rc == MMORE && (line != NULL || mapi_query_done(hdl) != MOK))
2934 0 : continue; /* get more data */
2935 :
2936 5519 : CHECK_RESULT(mid, hdl, buf, fp);
2937 :
2938 5519 : timerEnd();
2939 5519 : mapi_close_handle(hdl);
2940 5519 : hdl = NULL;
2941 108849 : } while (line != NULL);
2942 : /* reached on end of file */
2943 0 : assert(hdl == NULL);
2944 137 : bailout:
2945 137 : free(buf);
2946 : #ifdef HAVE_LIBREADLINE
2947 137 : if (prompt)
2948 0 : deinit_readline();
2949 : #endif
2950 137 : close_stream(fp);
2951 137 : return errseen;
2952 : }
2953 :
2954 : #ifdef HAVE_CURL
2955 : #include <curl/curl.h>
2956 :
2957 : #ifndef CURL_WRITEFUNC_ERROR
2958 : #define CURL_WRITEFUNC_ERROR 0
2959 : #endif
2960 :
2961 : static size_t
2962 0 : write_callback(char *buffer, size_t size, size_t nitems, void *userp)
2963 : {
2964 0 : stream *s = userp;
2965 :
2966 : /* size is expected to always be 1 */
2967 :
2968 0 : ssize_t sz = mnstr_write(s, buffer, size, nitems);
2969 0 : if (sz < 0)
2970 : return CURL_WRITEFUNC_ERROR; /* indicate failure to library */
2971 0 : return (size_t) sz * size;
2972 : }
2973 :
2974 : static stream *
2975 0 : open_urlstream(const char *url, char *errbuf)
2976 : {
2977 0 : CURL *handle;
2978 0 : stream *s;
2979 0 : CURLcode ret;
2980 :
2981 0 : s = buffer_wastream(NULL, url);
2982 0 : if (s == NULL) {
2983 0 : snprintf(errbuf, CURL_ERROR_SIZE, "could not allocate memory");
2984 0 : return NULL;
2985 : }
2986 :
2987 0 : if ((handle = curl_easy_init()) == NULL) {
2988 0 : mnstr_destroy(s);
2989 0 : snprintf(errbuf, CURL_ERROR_SIZE, "could not create CURL handle");
2990 0 : return NULL;
2991 : }
2992 :
2993 0 : errbuf[0] = 0;
2994 :
2995 0 : if ((ret = curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, errbuf)) != CURLE_OK ||
2996 0 : (ret = curl_easy_setopt(handle, CURLOPT_URL, url)) != CURLE_OK ||
2997 0 : (ret = curl_easy_setopt(handle, CURLOPT_WRITEDATA, s)) != CURLE_OK ||
2998 0 : (ret = curl_easy_setopt(handle, CURLOPT_VERBOSE, 0)) != CURLE_OK ||
2999 0 : (ret = curl_easy_setopt(handle, CURLOPT_NOSIGNAL, 1)) != CURLE_OK ||
3000 0 : (ret = curl_easy_setopt(handle, CURLOPT_FAILONERROR, 1)) != CURLE_OK ||
3001 0 : (ret = curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, write_callback)) != CURLE_OK ||
3002 0 : (ret = curl_easy_perform(handle)) != CURLE_OK) {
3003 0 : curl_easy_cleanup(handle);
3004 0 : mnstr_destroy(s);
3005 0 : if (errbuf[0] == 0)
3006 0 : snprintf(errbuf, CURL_ERROR_SIZE, "%s", curl_easy_strerror(ret));
3007 0 : return NULL;
3008 : }
3009 0 : curl_easy_cleanup(handle);
3010 0 : (void) mnstr_get_buffer(s); /* switch to read-only */
3011 0 : return s;
3012 : }
3013 : #endif
3014 :
3015 : struct privdata {
3016 : stream *f;
3017 : char *buf;
3018 : Mapi mid;
3019 : #ifdef HAVE_CURL
3020 : char errbuf[CURL_ERROR_SIZE];
3021 : #endif
3022 : };
3023 :
3024 : #define READSIZE (1 << 16)
3025 : //#define READSIZE (1 << 20)
3026 :
3027 : static char *
3028 179 : cvfilename(const char *filename)
3029 : {
3030 : #ifdef HAVE_ICONV
3031 179 : if (encoding) {
3032 0 : iconv_t cd = iconv_open(encoding, "UTF-8");
3033 :
3034 0 : if (cd != (iconv_t) -1) {
3035 0 : size_t len = strlen(filename);
3036 0 : size_t size = 4 * len;
3037 0 : char *from = (char *) filename;
3038 0 : char *r = malloc(size + 1);
3039 0 : char *p = r;
3040 :
3041 0 : if (r) {
3042 0 : if (iconv(cd, &from, &len, &p, &size) != (size_t) -1) {
3043 0 : iconv_close(cd);
3044 0 : *p = 0;
3045 0 : return r;
3046 : }
3047 0 : free(r);
3048 : }
3049 0 : iconv_close(cd);
3050 : }
3051 : }
3052 : #endif
3053 : /* couldn't use iconv for whatever reason; alternative is to
3054 : * use utf8towchar above to convert to a wide character string
3055 : * (wcs) and convert that to the locale-specific encoding
3056 : * using wcstombs or wcsrtombs (but preferably only if the
3057 : * locale's encoding is not UTF-8) */
3058 179 : return strdup(filename);
3059 : }
3060 :
3061 : static const char alpha[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
3062 : "abcdefghijklmnopqrstuvwxyz";
3063 :
3064 : static char *
3065 23787 : getfile(void *data, const char *filename, bool binary,
3066 : uint64_t offset, size_t *size)
3067 : {
3068 23787 : stream *f;
3069 23787 : char *buf;
3070 23787 : struct privdata *priv = data;
3071 23787 : ssize_t s;
3072 23787 : char *fname = NULL;
3073 :
3074 23787 : if (size)
3075 23786 : *size = 0; /* most returns require this */
3076 23787 : if (priv->buf == NULL) {
3077 28 : priv->buf = malloc(READSIZE);
3078 28 : if (priv->buf == NULL)
3079 : return "allocation failed in client";
3080 : }
3081 23787 : buf = priv->buf;
3082 23787 : if (filename != NULL) {
3083 104 : fname = cvfilename(filename);
3084 104 : if (fname == NULL)
3085 : return "allocation failed in client";
3086 104 : if (binary) {
3087 85 : f = open_rstream(fname);
3088 85 : assert(offset <= 1);
3089 : offset = 0;
3090 : } else {
3091 19 : f = open_rastream(fname);
3092 19 : if (f == NULL) {
3093 19 : size_t x;
3094 : /* simplistic check for URL
3095 : * (schema://...) */
3096 19 : if ((x = strspn(filename, alpha)) > 0
3097 19 : && filename[x] == ':'
3098 0 : && filename[x+1] == '/'
3099 0 : && filename[x+2] == '/') {
3100 : #ifdef HAVE_CURL
3101 0 : if (allow_remote) {
3102 0 : f = open_urlstream(filename, priv->errbuf);
3103 0 : if (f == NULL && priv->errbuf[0]) {
3104 0 : free(fname);
3105 0 : return priv->errbuf;
3106 : }
3107 : } else
3108 : #endif
3109 : {
3110 0 : free(fname);
3111 0 : return "client refuses to retrieve remote content";
3112 : }
3113 : }
3114 : }
3115 : #ifdef HAVE_ICONV
3116 0 : else if (encoding) {
3117 0 : stream *tmpf = f;
3118 0 : f = iconv_rstream(f, encoding, mnstr_name(f));
3119 0 : if (f == NULL)
3120 0 : close_stream(tmpf);
3121 : }
3122 : #endif
3123 : }
3124 104 : if (f == NULL) {
3125 19 : if (curfile != NULL) {
3126 19 : char *p = strrchr(curfile, '/');
3127 : #ifdef _MSC_VER
3128 : char *q = strrchr(curfile, '\\');
3129 : if (p == NULL || (q != NULL && q > p))
3130 : p = q;
3131 : #endif
3132 19 : if (p != NULL) {
3133 19 : size_t x = (size_t) (p - curfile) + strlen(fname) + 2;
3134 19 : char *b = malloc(x);
3135 19 : snprintf(b, x, "%.*s/%s", (int) (p - curfile), curfile, fname);
3136 19 : f = binary ? open_rstream(b) : open_rastream(b);
3137 19 : free(b);
3138 : }
3139 : }
3140 19 : if (f == NULL) {
3141 0 : free(fname);
3142 0 : return (char*) mnstr_peek_error(NULL);
3143 : }
3144 : }
3145 104 : free(fname);
3146 104 : while (offset > 1) {
3147 0 : if (state == INTERRUPT) {
3148 0 : close_stream(f);
3149 0 : return "interrupted";
3150 : }
3151 0 : s = mnstr_readline(f, buf, READSIZE);
3152 0 : if (s < 0) {
3153 0 : close_stream(f);
3154 0 : return "error reading file";
3155 : }
3156 0 : if (s == 0) {
3157 : /* reached EOF within offset lines */
3158 0 : close_stream(f);
3159 0 : return NULL;
3160 : }
3161 0 : if (buf[s - 1] == '\n')
3162 0 : offset--;
3163 : }
3164 104 : priv->f = f;
3165 : } else {
3166 23683 : f = priv->f;
3167 23683 : if (size == NULL) {
3168 : /* done reading before reaching EOF */
3169 1 : close_stream(f);
3170 1 : priv->f = NULL;
3171 1 : return NULL;
3172 : }
3173 : }
3174 23786 : if (state == INTERRUPT) {
3175 0 : close_stream(f);
3176 0 : priv->f = NULL;
3177 0 : (void) mapi_query_abort(mapi_get_active(priv->mid), 1);
3178 0 : return "interrupted";
3179 : }
3180 23786 : s = mnstr_read(f, buf, 1, READSIZE);
3181 23786 : if (s <= 0) {
3182 103 : close_stream(f);
3183 103 : priv->f = NULL;
3184 103 : if (s < 0) {
3185 0 : (void) mapi_query_abort(mapi_get_active(priv->mid), state == INTERRUPT ? 1 : 2);
3186 0 : return "error reading file";
3187 : }
3188 : return NULL;
3189 : }
3190 23683 : if (size)
3191 23683 : *size = (size_t) s;
3192 : return buf;
3193 : }
3194 :
3195 : static char *
3196 180886 : putfile(void *data, const char *filename, bool binary, const void *buf, size_t bufsize)
3197 : {
3198 180886 : struct privdata *priv = data;
3199 180886 : char *fname = NULL;
3200 :
3201 180886 : if (filename != NULL) {
3202 75 : fname = cvfilename(filename);
3203 75 : if (fname == NULL)
3204 : return "allocation failed in client";
3205 75 : stream *s = binary ? open_wstream(fname) : open_wastream(fname);
3206 75 : free(fname);
3207 75 : if (s == NULL)
3208 0 : return (char*)mnstr_peek_error(NULL);
3209 75 : priv->f = s;
3210 : #ifdef HAVE_ICONV
3211 75 : if (encoding) {
3212 0 : stream *f = priv->f;
3213 0 : priv->f = iconv_wstream(f, encoding, mnstr_name(f));
3214 0 : if (priv->f == NULL) {
3215 0 : close_stream(f);
3216 0 : return (char*)mnstr_peek_error(NULL);
3217 : }
3218 : }
3219 : #endif
3220 75 : if (state == INTERRUPT)
3221 0 : goto interrupted;
3222 75 : if (buf == NULL || bufsize == 0)
3223 : return NULL; /* successfully opened file */
3224 180811 : } else if (buf == NULL) {
3225 : /* done writing */
3226 75 : int flush = mnstr_flush(priv->f, MNSTR_FLUSH_DATA);
3227 75 : close_stream(priv->f);
3228 75 : priv->f = NULL;
3229 75 : return flush < 0 ? "error writing output" : NULL;
3230 : }
3231 180736 : if (state == INTERRUPT) {
3232 0 : char *fname;
3233 0 : interrupted:
3234 0 : fname = strdup(mnstr_name(priv->f));
3235 0 : close_stream(priv->f);
3236 0 : priv->f = NULL;
3237 0 : if (fname) {
3238 0 : if (MT_remove(fname) < 0)
3239 0 : perror(fname);
3240 0 : free(fname);
3241 : }
3242 0 : if (filename == NULL)
3243 0 : (void) mapi_query_abort(mapi_get_active(priv->mid), 1);
3244 0 : return "query aborted";
3245 : }
3246 180736 : if (mnstr_write(priv->f, buf, 1, bufsize) < (ssize_t) bufsize) {
3247 0 : close_stream(priv->f);
3248 0 : priv->f = NULL;
3249 0 : return "error writing output";
3250 : }
3251 : return NULL; /* success */
3252 : }
3253 :
3254 : static _Noreturn void usage(const char *prog, int xit);
3255 :
3256 : static void
3257 0 : usage(const char *prog, int xit)
3258 : {
3259 0 : mnstr_printf(stderr_stream, "Usage: %s [ options ] [ file or database [ file ... ] ]\n", prog);
3260 0 : mnstr_printf(stderr_stream, "\nOptions are:\n");
3261 : #ifdef HAVE_SYS_UN_H
3262 0 : mnstr_printf(stderr_stream, " -h hostname | --host=hostname host or UNIX domain socket to connect to\n");
3263 : #else
3264 : mnstr_printf(stderr_stream, " -h hostname | --host=hostname host to connect to\n");
3265 : #endif
3266 0 : mnstr_printf(stderr_stream, " -p portnr | --port=portnr port to connect to\n");
3267 0 : mnstr_printf(stderr_stream, " -u user | --user=user user id\n");
3268 0 : mnstr_printf(stderr_stream, " -d database | --database=database database to connect to (may be URI)\n");
3269 :
3270 0 : mnstr_printf(stderr_stream, " -e | --echo echo the query\n");
3271 : #ifdef HAVE_ICONV
3272 0 : mnstr_printf(stderr_stream, " -E charset | --encoding=charset specify encoding (character set) of the terminal\n");
3273 : #endif
3274 0 : mnstr_printf(stderr_stream, " -f kind | --format=kind specify output format {csv,tab,raw,sql,xml,trash,rowcount}\n");
3275 0 : mnstr_printf(stderr_stream, " -H | --history load/save cmdline history (default off)\n");
3276 0 : mnstr_printf(stderr_stream, " -i | --interactive interpret `\\' commands on stdin\n");
3277 0 : mnstr_printf(stderr_stream, " -t | --timer=format use time formatting {none,clock,performance} (none is default)\n");
3278 0 : mnstr_printf(stderr_stream, " -l language | --language=lang {sql,mal}\n");
3279 0 : mnstr_printf(stderr_stream, " -L logfile | --log=logfile save client/server interaction\n");
3280 0 : mnstr_printf(stderr_stream, " -s stmt | --statement=stmt run single statement\n");
3281 0 : mnstr_printf(stderr_stream, " -X | --Xdebug trace mapi network interaction\n");
3282 0 : mnstr_printf(stderr_stream, " -z | --timezone do not tell server our timezone\n");
3283 : #ifdef HAVE_POPEN
3284 0 : mnstr_printf(stderr_stream, " -| cmd | --pager=cmd for pagination\n");
3285 : #endif
3286 0 : mnstr_printf(stderr_stream, " -v | --version show version information and exit\n");
3287 0 : mnstr_printf(stderr_stream, " -? | --help show this usage message\n");
3288 :
3289 0 : mnstr_printf(stderr_stream, "\nSQL specific options \n");
3290 0 : mnstr_printf(stderr_stream, " -n nullstr | --null=nullstr change NULL representation for sql, csv and tab output modes\n");
3291 0 : mnstr_printf(stderr_stream, " -a | --autocommit turn off autocommit mode\n");
3292 0 : mnstr_printf(stderr_stream, " -R | --allow-remote allow remote content\n");
3293 0 : mnstr_printf(stderr_stream, " -r nr | --rows=nr for pagination\n");
3294 0 : mnstr_printf(stderr_stream, " -w nr | --width=nr for pagination\n");
3295 0 : mnstr_printf(stderr_stream, " -D | --dump create an SQL dump\n");
3296 0 : mnstr_printf(stderr_stream, " -N | --inserts use INSERT INTO statements when dumping\n");
3297 0 : mnstr_printf(stderr_stream, "The file argument can be - for stdin\n");
3298 0 : exit(xit);
3299 : }
3300 :
3301 : static inline bool
3302 1 : isfile(FILE *fp)
3303 : {
3304 1 : struct stat stb;
3305 1 : if (fstat(fileno(fp), &stb) < 0 ||
3306 1 : (stb.st_mode & S_IFMT) != S_IFREG) {
3307 0 : fclose(fp);
3308 0 : return false;
3309 : }
3310 : return true;
3311 : }
3312 :
3313 : static bool
3314 0 : interrupted(void *m)
3315 : {
3316 0 : Mapi mid = m;
3317 0 : if (state == INTERRUPT) {
3318 0 : mnstr_set_error(mapi_get_from(mid), MNSTR_INTERRUPT, NULL);
3319 0 : return true;
3320 : }
3321 : return false;
3322 : }
3323 :
3324 : static void
3325 0 : catch_interrupts(Mapi mid)
3326 : {
3327 : #ifdef HAVE_SIGACTION
3328 0 : struct sigaction sa;
3329 0 : (void) sigemptyset(&sa.sa_mask);
3330 0 : sa.sa_flags = 0;
3331 0 : sa.sa_handler = sigint_handler;
3332 0 : if (sigaction(SIGINT, &sa, NULL) == -1) {
3333 0 : perror("Could not install signal handler");
3334 : }
3335 : #else
3336 : if (signal(SIGINT, sigint_handler) == SIG_ERR) {
3337 : perror("Could not install signal handler");
3338 : }
3339 : #endif
3340 0 : mapi_set_rtimeout(mid, 100, interrupted, mid);
3341 0 : }
3342 :
3343 : int
3344 : #ifdef _MSC_VER
3345 : wmain(int argc, wchar_t **wargv)
3346 : #else
3347 175 : main(int argc, char **argv)
3348 : #endif
3349 : {
3350 175 : int port = 0;
3351 175 : const char *user = NULL;
3352 175 : const char *passwd = NULL;
3353 175 : const char *host = NULL;
3354 175 : const char *command = NULL;
3355 175 : const char *dbname = NULL;
3356 175 : const char *output = NULL; /* output format as string */
3357 175 : DotMonetdb dotfile = {0};
3358 175 : stream *s = NULL;
3359 175 : bool trace = false;
3360 175 : bool dump = false;
3361 175 : bool useinserts = false;
3362 175 : int c = 0;
3363 175 : Mapi mid;
3364 175 : bool save_history = false;
3365 175 : bool interactive = false;
3366 175 : bool has_fileargs = false;
3367 175 : int option_index = 0;
3368 175 : bool settz = true;
3369 175 : bool autocommit = true; /* autocommit mode default on */
3370 175 : bool user_set_as_flag = false;
3371 175 : bool passwd_set_as_flag = false;
3372 175 : static const struct option long_options[] = {
3373 : {"autocommit", 0, 0, 'a'},
3374 : {"database", 1, 0, 'd'},
3375 : {"dump", 0, 0, 'D'},
3376 : {"inserts", 0, 0, 'N'},
3377 : {"echo", 0, 0, 'e'},
3378 : #ifdef HAVE_ICONV
3379 : {"encoding", 1, 0, 'E'},
3380 : #endif
3381 : {"format", 1, 0, 'f'},
3382 : {"help", 0, 0, '?'},
3383 : {"history", 0, 0, 'H'},
3384 : {"host", 1, 0, 'h'},
3385 : {"interactive", 0, 0, 'i'},
3386 : {"timer", 1, 0, 't'},
3387 : {"language", 1, 0, 'l'},
3388 : {"log", 1, 0, 'L'},
3389 : {"null", 1, 0, 'n'},
3390 : #ifdef HAVE_POPEN
3391 : {"pager", 1, 0, '|'},
3392 : #endif
3393 : {"port", 1, 0, 'p'},
3394 : {"rows", 1, 0, 'r'},
3395 : {"statement", 1, 0, 's'},
3396 : {"user", 1, 0, 'u'},
3397 : {"version", 0, 0, 'v'},
3398 : {"width", 1, 0, 'w'},
3399 : {"Xdebug", 0, 0, 'X'},
3400 : {"timezone", 0, 0, 'z'},
3401 : {"allow-remote", 0, 0, 'R'},
3402 : {0, 0, 0, 0}
3403 : };
3404 :
3405 : #ifdef _MSC_VER
3406 : char **argv = malloc((argc + 1) * sizeof(char *));
3407 : if (argv == NULL) {
3408 : fprintf(stderr, "cannot allocate memory for argument conversion\n");
3409 : exit(1);
3410 : }
3411 : for (int i = 0; i < argc; i++) {
3412 : if ((argv[i] = wchartoutf8(wargv[i])) == NULL) {
3413 : fprintf(stderr, "cannot convert argument to UTF-8\n");
3414 : exit(1);
3415 : }
3416 : }
3417 : argv[argc] = NULL;
3418 : #endif
3419 : #ifndef WIN32
3420 : /* don't set locale on Windows: setting the locale like this
3421 : * causes the output to be converted (we could set it to
3422 : * ".OCP" if we knew for sure that we were running in a cmd
3423 : * window) */
3424 175 : if(setlocale(LC_CTYPE, "") == NULL) {
3425 0 : fprintf(stderr, "error: could not set locale\n");
3426 0 : exit(2);
3427 : }
3428 :
3429 : /* Windows doesn't know about SIGPIPE */
3430 175 : if (signal(SIGPIPE, SIG_IGN) == SIG_ERR)
3431 0 : perror("sigaction");
3432 : #endif
3433 175 : if (mnstr_init() < 0) {
3434 0 : fprintf(stderr, "error: could not initialize streams library");
3435 0 : exit(2);
3436 : }
3437 :
3438 175 : toConsole = stdout_stream = stdout_wastream();
3439 175 : stderr_stream = stderr_wastream();
3440 175 : if(!stdout_stream || !stderr_stream) {
3441 0 : if(stdout_stream)
3442 0 : close_stream(stdout_stream);
3443 0 : if(stderr_stream)
3444 0 : close_stream(stderr_stream);
3445 0 : fprintf(stderr, "error: could not open an output stream\n");
3446 0 : exit(2);
3447 : }
3448 :
3449 : /* parse config file first, command line options override */
3450 175 : parse_dotmonetdb(&dotfile);
3451 175 : user = dotfile.user;
3452 175 : passwd = dotfile.passwd;
3453 175 : dbname = dotfile.dbname;
3454 175 : language = dotfile.language;
3455 175 : host = dotfile.host;
3456 175 : save_history = dotfile.save_history;
3457 175 : output = dotfile.output;
3458 175 : pagewidth = dotfile.pagewidth;
3459 175 : port = dotfile.port;
3460 175 : pagewidthset = pagewidth != 0;
3461 175 : if (language) {
3462 0 : if (strcmp(language, "sql") == 0) {
3463 0 : mode = SQL;
3464 0 : } else if (strcmp(language, "mal") == 0) {
3465 0 : mode = MAL;
3466 : }
3467 : } else {
3468 175 : language = "sql";
3469 175 : mode = SQL;
3470 : }
3471 :
3472 3314 : while ((c = getopt_long(argc, argv, "ad:De"
3473 : #ifdef HAVE_ICONV
3474 : "E:"
3475 : #endif
3476 : "f:h:Hil:L:n:Np:P:r:Rs:t:u:vw:Xz"
3477 : #ifdef HAVE_POPEN
3478 : "|:"
3479 : #endif
3480 : "?",
3481 1657 : long_options, &option_index)) != -1) {
3482 1483 : switch (c) {
3483 : case 0:
3484 : /* only needed for options that only have a
3485 : * long form */
3486 : break;
3487 : case 'a':
3488 1482 : autocommit = false;
3489 : break;
3490 173 : case 'd':
3491 173 : assert(optarg);
3492 : dbname = optarg;
3493 : break;
3494 7 : case 'D':
3495 7 : dump = true;
3496 7 : break;
3497 131 : case 'e':
3498 131 : echoquery = true;
3499 131 : break;
3500 : #ifdef HAVE_ICONV
3501 155 : case 'E':
3502 155 : assert(optarg);
3503 155 : encoding = optarg;
3504 155 : break;
3505 : #endif
3506 220 : case 'f':
3507 220 : assert(optarg);
3508 : output = optarg; /* output format */
3509 : break;
3510 153 : case 'h':
3511 153 : assert(optarg);
3512 : host = optarg;
3513 : break;
3514 0 : case 'H':
3515 0 : save_history = true;
3516 0 : break;
3517 135 : case 'i':
3518 135 : interactive = true;
3519 135 : break;
3520 162 : case 'l':
3521 162 : assert(optarg);
3522 : /* accept unambiguous prefix of language */
3523 162 : if (strcmp(optarg, "sql") == 0 ||
3524 0 : strcmp(optarg, "sq") == 0 ||
3525 0 : strcmp(optarg, "s") == 0) {
3526 162 : language = "sql";
3527 162 : mode = SQL;
3528 0 : } else if (strcmp(optarg, "mal") == 0 ||
3529 0 : strcmp(optarg, "ma") == 0) {
3530 0 : language = "mal";
3531 0 : mode = MAL;
3532 0 : } else if (strcmp(optarg, "msql") == 0) {
3533 0 : language = "msql";
3534 0 : mode = MAL;
3535 : } else {
3536 0 : mnstr_printf(stderr_stream, "language option needs to be sql or mal\n");
3537 0 : exit(-1);
3538 : }
3539 : break;
3540 14 : case 'L':
3541 14 : assert(optarg);
3542 14 : logfile = strdup(optarg);
3543 14 : break;
3544 0 : case 'n':
3545 0 : assert(optarg);
3546 0 : nullstring = optarg;
3547 0 : break;
3548 0 : case 'N':
3549 0 : useinserts = true;
3550 0 : break;
3551 154 : case 'p':
3552 154 : assert(optarg);
3553 154 : port = atoi(optarg);
3554 154 : break;
3555 0 : case 'P':
3556 0 : assert(optarg);
3557 : passwd = optarg;
3558 : passwd_set_as_flag = true;
3559 : break;
3560 0 : case 'r':
3561 0 : assert(optarg);
3562 0 : rowsperpage = atoi(optarg);
3563 0 : break;
3564 0 : case 'R':
3565 0 : allow_remote = true;
3566 0 : break;
3567 16 : case 's':
3568 16 : assert(optarg);
3569 : command = optarg;
3570 : break;
3571 155 : case 't':
3572 155 : if (optarg != NULL) {
3573 155 : if (strcmp(optarg,"none") == 0) {
3574 155 : timermode = T_NONE;
3575 0 : } else if (strcmp(optarg,"clock") == 0) {
3576 0 : timermode = T_CLOCK;
3577 0 : } else if (strcmp(optarg, "perf") == 0 || strcmp(optarg, "performance") == 0) {
3578 0 : timermode = T_PERF;
3579 0 : } else if (*optarg != '\0') {
3580 0 : mnstr_printf(stderr_stream, "warning: invalid argument to -t: %s\n",
3581 : optarg);
3582 : }
3583 : }
3584 : break;
3585 0 : case 'u':
3586 0 : assert(optarg);
3587 : user = optarg;
3588 : user_set_as_flag = true;
3589 : break;
3590 1 : case 'v': {
3591 1 : mnstr_printf(toConsole,
3592 : "mclient, the MonetDB interactive "
3593 : "terminal, version %s", MONETDB_VERSION);
3594 : #ifdef MONETDB_RELEASE
3595 : mnstr_printf(toConsole, " (%s)", MONETDB_RELEASE);
3596 : #else
3597 1 : const char *rev = mercurial_revision();
3598 1 : if (strcmp(rev, "Unknown") != 0)
3599 0 : mnstr_printf(toConsole, " (hg id: %s)", rev);
3600 : #endif
3601 1 : mnstr_printf(toConsole, "\n");
3602 : #ifdef HAVE_LIBREADLINE
3603 1 : mnstr_printf(toConsole,
3604 : "support for command-line editing "
3605 : "compiled-in\n");
3606 : #endif
3607 : #ifdef HAVE_ICONV
3608 : #ifdef HAVE_NL_LANGINFO
3609 1 : if (encoding == NULL)
3610 1 : encoding = nl_langinfo(CODESET);
3611 : #endif
3612 1 : mnstr_printf(toConsole,
3613 : "character encoding: %s\n",
3614 1 : encoding ? encoding : "utf-8 (default)");
3615 : #endif
3616 1 : mnstr_printf(toConsole, "using mapi library %s\n",
3617 : mapi_get_mapi_version());
3618 1 : destroy_dotmonetdb(&dotfile);
3619 1 : return 0;
3620 : }
3621 0 : case 'w':
3622 0 : assert(optarg);
3623 0 : pagewidth = atoi(optarg);
3624 0 : pagewidthset = pagewidth != 0;
3625 0 : break;
3626 0 : case 'X':
3627 0 : trace = true;
3628 0 : break;
3629 7 : case 'z':
3630 7 : settz = false;
3631 7 : break;
3632 : #ifdef HAVE_POPEN
3633 0 : case '|':
3634 0 : assert(optarg);
3635 0 : pager = optarg;
3636 0 : break;
3637 : #endif
3638 0 : case '?':
3639 : /* a bit of a hack: look at the option that the
3640 : * current `c' is based on and see if we recognize
3641 : * it: if -? or --help, exit with 0, else with -1 */
3642 0 : usage(argv[0], strcmp(argv[optind - 1], "-?") == 0 || strcmp(argv[optind - 1], "--help") == 0 ? 0 : -1);
3643 : /* not reached */
3644 0 : default:
3645 0 : usage(argv[0], -1);
3646 : /* not reached */
3647 : }
3648 : }
3649 174 : if (passwd_set_as_flag &&
3650 0 : (output == NULL || strcmp(output, "test") != 0)) {
3651 0 : usage(argv[0], -1);
3652 : /* not reached */
3653 : }
3654 :
3655 : #ifdef HAVE_ICONV
3656 : #ifdef HAVE_NL_LANGINFO
3657 174 : if (encoding == NULL)
3658 19 : encoding = nl_langinfo(CODESET);
3659 : #endif
3660 174 : if (encoding != NULL && strcasecmp(encoding, "utf-8") == 0)
3661 170 : encoding = NULL;
3662 174 : if (encoding != NULL) {
3663 4 : stream *s = iconv_wstream(toConsole, encoding, "stdout");
3664 4 : if (s == NULL || mnstr_errnr(s) != MNSTR_NO__ERROR) {
3665 0 : mnstr_printf(stderr_stream, "warning: cannot convert local character set %s to UTF-8\n", encoding);
3666 0 : close_stream(s);
3667 : } else
3668 4 : toConsole = s;
3669 4 : stdout_stream = toConsole;
3670 : }
3671 : #endif /* HAVE_ICONV */
3672 :
3673 : /* when config file would provide defaults */
3674 174 : if (user_set_as_flag) {
3675 0 : if (passwd && !passwd_set_as_flag) {
3676 174 : passwd = NULL;
3677 : }
3678 : }
3679 :
3680 174 : char *user_allocated = NULL;
3681 174 : if (user == NULL) {
3682 0 : user_allocated = simple_prompt("user", BUFSIZ, 1, prompt_getlogin());
3683 0 : user = user_allocated;
3684 : }
3685 174 : char *passwd_allocated = NULL;
3686 174 : if (passwd == NULL) {
3687 0 : passwd_allocated = simple_prompt("password", BUFSIZ, 0, NULL);
3688 0 : passwd = passwd_allocated;
3689 : }
3690 :
3691 174 : c = 0;
3692 174 : has_fileargs = optind != argc;
3693 :
3694 174 : if (dbname == NULL && has_fileargs && strcmp(argv[optind], "-") != 0) {
3695 1 : s = open_rastream(argv[optind]);
3696 1 : if (s == NULL || !isfile(getFile(s))) {
3697 0 : mnstr_close(s);
3698 0 : s = NULL;
3699 : }
3700 1 : if (s == NULL) {
3701 0 : dbname = argv[optind];
3702 0 : optind++;
3703 0 : has_fileargs = optind != argc;
3704 : } else {
3705 1 : curfile = argv[optind];
3706 : }
3707 : }
3708 :
3709 174 : if (dbname != NULL && strchr(dbname, ':') != NULL) {
3710 20 : mid = mapi_mapiuri(dbname, user, passwd, language);
3711 : } else {
3712 154 : mid = mapi_mapi(host, port, user, passwd, language, dbname);
3713 : }
3714 174 : free(user_allocated);
3715 174 : user_allocated = NULL;
3716 174 : free(passwd_allocated);
3717 174 : passwd_allocated = NULL;
3718 174 : user = NULL;
3719 174 : passwd = NULL;
3720 174 : dbname = NULL;
3721 :
3722 174 : if (mid == NULL) {
3723 0 : mnstr_printf(stderr_stream, "failed to allocate Mapi structure\n");
3724 0 : exit(2);
3725 : }
3726 :
3727 174 : mapi_cache_limit(mid, 1000);
3728 174 : mapi_setAutocommit(mid, autocommit);
3729 174 : if (mode == SQL && !settz)
3730 7 : mapi_set_time_zone(mid, 0);
3731 174 : if (output) {
3732 159 : setFormatter(output);
3733 159 : if (mode == SQL)
3734 159 : mapi_set_size_header(mid, strcmp(output, "raw") == 0);
3735 : } else {
3736 15 : if (mode == SQL) {
3737 15 : setFormatter("sql");
3738 15 : mapi_set_size_header(mid, false);
3739 : } else {
3740 0 : setFormatter("raw");
3741 : }
3742 : }
3743 :
3744 174 : if (logfile)
3745 14 : mapi_log(mid, logfile);
3746 :
3747 174 : if (mapi_error(mid) == MOK)
3748 174 : mapi_reconnect(mid); /* actually, initial connect */
3749 :
3750 174 : if (mapi_error(mid)) {
3751 14 : if (trace)
3752 0 : mapi_explain(mid, stderr);
3753 : else
3754 14 : mnstr_printf(stderr_stream, "%s\n", mapi_error_str(mid));
3755 14 : exit(2);
3756 : }
3757 160 : if (dump) {
3758 7 : if (mode == SQL) {
3759 7 : exit(dump_database(mid, toConsole, NULL, NULL, false, useinserts, false));
3760 : } else {
3761 0 : mnstr_printf(stderr_stream, "Dump only supported for SQL\n");
3762 0 : exit(1);
3763 : }
3764 : }
3765 :
3766 153 : struct privdata priv;
3767 153 : priv = (struct privdata) {.mid = mid};
3768 153 : mapi_setfilecallback2(mid, getfile, putfile, &priv);
3769 :
3770 153 : mapi_trace(mid, trace);
3771 : /* give the user a welcome message with some general info */
3772 153 : if (!has_fileargs && command == NULL && isatty(fileno(stdin))) {
3773 0 : char *lang;
3774 :
3775 0 : catch_interrupts(mid);
3776 :
3777 0 : if (mode == SQL) {
3778 : lang = "/SQL";
3779 : } else {
3780 0 : lang = "";
3781 : }
3782 :
3783 0 : mnstr_printf(toConsole,
3784 : "Welcome to mclient, the MonetDB%s "
3785 : "interactive terminal (%s)\n",
3786 : lang,
3787 : #ifdef MONETDB_RELEASE
3788 : MONETDB_RELEASE
3789 : #else
3790 : "unreleased"
3791 : #endif
3792 : );
3793 :
3794 0 : if (mode == SQL)
3795 0 : dump_version(mid, toConsole, "Database:");
3796 :
3797 0 : mnstr_printf(toConsole, "FOLLOW US on https://github.com/MonetDB/MonetDB\n"
3798 : "Type \\q to quit, \\? for a list of available commands\n");
3799 0 : if (mode == SQL)
3800 0 : mnstr_printf(toConsole, "auto commit mode: %s\n",
3801 0 : mapi_get_autocommit(mid) ? "on" : "off");
3802 : }
3803 :
3804 153 : if (command != NULL) {
3805 : #if !defined(_MSC_VER) && defined(HAVE_ICONV)
3806 : /* no need on Windows: using wmain interface */
3807 16 : iconv_t cd_in;
3808 16 : char *command_allocated = NULL;
3809 :
3810 16 : if (encoding != NULL &&
3811 8 : (cd_in = iconv_open("utf-8", encoding)) != (iconv_t) -1) {
3812 4 : char *from = (char *) command;
3813 4 : size_t fromlen = strlen(from);
3814 4 : int factor = 4;
3815 4 : size_t tolen = factor * fromlen + 1;
3816 4 : char *to = malloc(tolen);
3817 :
3818 4 : if (to == NULL) {
3819 0 : mnstr_printf(stderr_stream,"Malloc in main failed");
3820 0 : exit(2);
3821 : }
3822 :
3823 4 : try_again:
3824 4 : command_allocated = to;
3825 4 : if (iconv(cd_in, &from, &fromlen, &to, &tolen) == (size_t) -1) {
3826 0 : switch (errno) {
3827 0 : case EILSEQ:
3828 : /* invalid multibyte sequence */
3829 0 : mnstr_printf(stderr_stream, "Illegal input sequence in command line\n");
3830 0 : exit(-1);
3831 0 : case E2BIG:
3832 : /* output buffer too small */
3833 0 : from = (char *) command;
3834 0 : fromlen = strlen(from);
3835 0 : factor *= 2;
3836 0 : tolen = factor * fromlen + 1;
3837 0 : free(command_allocated);
3838 0 : to = malloc(tolen);
3839 0 : if (to == NULL) {
3840 0 : mnstr_printf(stderr_stream,"Malloc in main failed");
3841 0 : exit(2);
3842 : }
3843 0 : goto try_again;
3844 0 : case EINVAL:
3845 : /* incomplete multibyte sequence */
3846 0 : mnstr_printf(stderr_stream, "Incomplete input sequence on command line\n");
3847 0 : exit(-1);
3848 : default:
3849 : break;
3850 : }
3851 : }
3852 4 : command = command_allocated;
3853 4 : *to = 0;
3854 4 : iconv_close(cd_in);
3855 12 : } else if (encoding)
3856 0 : mnstr_printf(stderr_stream, "warning: cannot convert local character set %s to UTF-8\n", encoding);
3857 : #endif
3858 : /* execute from command-line, need interactive to know whether
3859 : * to keep the mapi handle open */
3860 16 : timerStart();
3861 16 : c = doRequest(mid, command);
3862 16 : timerEnd();
3863 : #if !defined(_MSC_VER) && defined(HAVE_ICONV)
3864 16 : free(command_allocated);
3865 : #endif
3866 : }
3867 :
3868 153 : if (optind < argc) {
3869 : /* execute from file(s) */
3870 8 : while (optind < argc) {
3871 4 : const char *arg = argv[optind];
3872 :
3873 4 : if (s == NULL) {
3874 3 : if (strcmp(arg, "-") == 0) {
3875 0 : catch_interrupts(mid);
3876 0 : s = stdin_rastream();
3877 : } else {
3878 3 : s = open_rastream(arg);
3879 3 : curfile = arg;
3880 : }
3881 : }
3882 3 : if (s == NULL) {
3883 0 : mnstr_printf(stderr_stream, "%s: cannot open: %s\n", arg, mnstr_peek_error(NULL));
3884 0 : c |= 1;
3885 0 : optind++;
3886 0 : curfile = NULL;
3887 0 : continue;
3888 : }
3889 : // doFile closes 's'.
3890 4 : c |= doFile(mid, s, useinserts, interactive, save_history);
3891 4 : s = NULL;
3892 4 : optind++;
3893 : }
3894 149 : } else if (command && mapi_get_active(mid))
3895 0 : c = doFileBulk(mid, NULL);
3896 :
3897 153 : if (!has_fileargs && command == NULL) {
3898 133 : s = stdin_rastream();
3899 133 : if(!s) {
3900 0 : mapi_destroy(mid);
3901 0 : mnstr_destroy(stdout_stream);
3902 0 : mnstr_destroy(stderr_stream);
3903 0 : fprintf(stderr,"Failed to open stream for stdin\n");
3904 0 : exit(2);
3905 : }
3906 133 : c = doFile(mid, s, useinserts, interactive, save_history);
3907 133 : s = NULL;
3908 : }
3909 :
3910 153 : mapi_destroy(mid);
3911 153 : if (toConsole != stdout_stream && toConsole != stderr_stream)
3912 1 : close_stream(toConsole);
3913 153 : mnstr_destroy(stdout_stream);
3914 153 : mnstr_destroy(stderr_stream);
3915 153 : if (priv.buf != NULL)
3916 28 : free(priv.buf);
3917 :
3918 153 : destroy_dotmonetdb(&dotfile);
3919 :
3920 153 : return c;
3921 : }
|