LCOV - code coverage report
Current view: top level - clients/mapiclient - mclient.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 900 2176 41.4 %
Date: 2024-04-26 00:35:57 Functions: 28 44 63.6 %

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

Generated by: LCOV version 1.14