LCOV - code coverage report
Current view: top level - clients/mapiclient - mclient.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 960 2209 43.5 %
Date: 2024-12-20 21:24:02 Functions: 30 46 65.2 %

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

Generated by: LCOV version 1.14