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