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