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