LCOV - code coverage report
Current view: top level - clients/mapiclient - ReadlineTools.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 0 178 0.0 %
Date: 2021-10-13 02:24:04 Functions: 0 12 0.0 %

          Line data    Source code
       1             : /*
       2             :  * This Source Code Form is subject to the terms of the Mozilla Public
       3             :  * License, v. 2.0.  If a copy of the MPL was not distributed with this
       4             :  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
       5             :  *
       6             :  * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
       7             :  */
       8             : 
       9             : /*
      10             :  * Readline specific stuff
      11             :  */
      12             : #include "monetdb_config.h"
      13             : 
      14             : #ifdef HAVE_LIBREADLINE
      15             : #include <fcntl.h>
      16             : #include <unistd.h>
      17             : 
      18             : #include <readline/readline.h>
      19             : #include <readline/history.h>
      20             : #include "ReadlineTools.h"
      21             : #define LIBMUTILS 1
      22             : #include "mutils.h"
      23             : 
      24             : #ifdef HAVE_STRINGS_H
      25             : #include <strings.h>              /* for strncasecmp */
      26             : #endif
      27             : 
      28             : #ifndef NATIVE_WIN32
      29             : /* for umask */
      30             : #include <sys/types.h>
      31             : #include <sys/stat.h>
      32             : #endif
      33             : 
      34             : static const char *sql_commands[] = {
      35             :         "SELECT",
      36             :         "INSERT",
      37             :         "UPDATE",
      38             :         "SET",
      39             :         "DELETE",
      40             :         "COMMIT",
      41             :         "ROLLBACK",
      42             :         "DROP TABLE",
      43             :         "CREATE",
      44             :         "ALTER",
      45             :         "RELEASE SAVEPOINT",
      46             :         "START TRANSACTION",
      47             :         0,
      48             : };
      49             : 
      50             : static Mapi _mid;
      51             : static char _history_file[FILENAME_MAX];
      52             : static bool _save_history = false;
      53             : static const char *language;
      54             : 
      55             : static char *
      56           0 : sql_tablename_generator(const char *text, int state)
      57             : {
      58             : 
      59             :         static int64_t seekpos, rowcount;
      60             :         static size_t len;
      61             :         static MapiHdl table_hdl;
      62             : 
      63           0 :         if (!state) {
      64             :                 char *query;
      65             : 
      66           0 :                 seekpos = 0;
      67           0 :                 len = strlen(text);
      68           0 :                 if ((query = malloc(len + 150)) == NULL)
      69             :                         return NULL;
      70           0 :                 snprintf(query, len + 150, "SELECT t.\"name\", s.\"name\" FROM \"sys\".\"tables\" t, \"sys\".\"schemas\" s where t.system = FALSE AND t.schema_id = s.id AND t.\"name\" like '%s%%'", text);
      71           0 :                 table_hdl = mapi_query(_mid, query);
      72           0 :                 free(query);
      73           0 :                 if (table_hdl == NULL || mapi_error(_mid)) {
      74           0 :                         if (table_hdl) {
      75           0 :                                 mapi_explain_query(table_hdl, stderr);
      76           0 :                                 mapi_close_handle(table_hdl);
      77             :                         } else
      78           0 :                                 mapi_explain(_mid, stderr);
      79           0 :                         return NULL;
      80             :                 }
      81           0 :                 mapi_fetch_all_rows(table_hdl);
      82           0 :                 rowcount = mapi_get_row_count(table_hdl);
      83             :         }
      84             : 
      85           0 :         while (seekpos < rowcount) {
      86           0 :                 if (mapi_seek_row(table_hdl, seekpos++, MAPI_SEEK_SET) != MOK ||
      87           0 :                     mapi_fetch_row(table_hdl) <= 0)
      88           0 :                         continue;
      89           0 :                 return strdup(mapi_fetch_field(table_hdl, 0));
      90             :         }
      91             : 
      92             :         return NULL;
      93             : }
      94             : 
      95             : /* SQL commands (at start of line) */
      96             : static char *
      97           0 : sql_command_generator(const char *text, int state)
      98             : {
      99             :         static size_t idx, len;
     100             :         const char *name;
     101             : 
     102           0 :         if (!state) {
     103           0 :                 idx = 0;
     104           0 :                 len = strlen(text);
     105             :         }
     106             : 
     107           0 :         while ((name = sql_commands[idx++])) {
     108           0 :                 if (strncasecmp(name, text, len) == 0)
     109           0 :                         return strdup(name);
     110             :         }
     111             : 
     112             :         return NULL;
     113             : }
     114             : 
     115             : static char **
     116           0 : sql_completion(const char *text, int start, int end)
     117             : {
     118             :         char **matches;
     119             : 
     120             :         matches = (char **) NULL;
     121             : 
     122             :         (void) end;
     123             : 
     124             :         /* FIXME: Nice, context-sensitive completion strategy should go here */
     125           0 :         if (strcmp(language, "sql") == 0) {
     126           0 :                 if (start == 0) {
     127           0 :                         matches = rl_completion_matches(text, sql_command_generator);
     128             :                 } else {
     129           0 :                         matches = rl_completion_matches(text, sql_tablename_generator);
     130             :                 }
     131             :         }
     132           0 :         if (strcmp(language, "mal") == 0) {
     133           0 :                 matches = rl_completion_matches(text, sql_tablename_generator);
     134             :         }
     135             : 
     136           0 :         return (matches);
     137             : }
     138             : 
     139             : /* The MAL completion help */
     140             : 
     141             : static const char *mal_commands[] = {
     142             :         "address",
     143             :         "atom",
     144             :         "barrier",
     145             :         "catch",
     146             :         "command",
     147             :         "comment",
     148             :         "exit",
     149             :         "end",
     150             :         "function",
     151             :         "factory",
     152             :         "leave",
     153             :         "pattern",
     154             :         "module",
     155             :         "raise",
     156             :         "redo",
     157             :         0
     158             : };
     159             : 
     160             : #ifdef illegal_ESC_binding
     161             : /* see also init_readline() below */
     162             : static int
     163             : mal_help(int cnt, int key)
     164             : {
     165             :         char *name, *c, *buf;
     166             :         int64_t seekpos = 0, rowcount;
     167             :         MapiHdl table_hdl;
     168             : 
     169             :         (void) cnt;
     170             :         (void) key;
     171             : 
     172             :         c = rl_line_buffer + strlen(rl_line_buffer) - 1;
     173             :         while (c > rl_line_buffer && isspace((unsigned char) *c))
     174             :                 c--;
     175             :         while (c > rl_line_buffer && !isspace((unsigned char) *c))
     176             :                 c--;
     177             :         if ((buf = malloc(strlen(c) + 20)) == NULL)
     178             :                 return 0;
     179             :         snprintf(buf, strlen(c) + 20, "manual.help(\"%s\");", c);
     180             :         table_hdl = mapi_query(_mid, buf);
     181             :         free(buf);
     182             :         if (table_hdl == NULL || mapi_error(_mid)) {
     183             :                 if (table_hdl) {
     184             :                         mapi_explain_query(table_hdl, stderr);
     185             :                         mapi_close_handle(table_hdl);
     186             :                 } else
     187             :                         mapi_explain(_mid, stderr);
     188             :                 return 0;
     189             :         }
     190             :         mapi_fetch_all_rows(table_hdl);
     191             :         rowcount = mapi_get_row_count(table_hdl);
     192             : 
     193             :         printf("\n");
     194             :         while (seekpos < rowcount) {
     195             :                 if (mapi_seek_row(table_hdl, seekpos++, MAPI_SEEK_SET) != MOK ||
     196             :                     mapi_fetch_row(table_hdl) <= 0)
     197             :                         continue;
     198             :                 name = mapi_fetch_field(table_hdl, 0);
     199             :                 if (name)
     200             :                         printf("%s\n", name);
     201             :         }
     202             :         return key;
     203             : }
     204             : #endif
     205             : 
     206             : static char *
     207           0 : mal_command_generator(const char *text, int state)
     208             : {
     209             : 
     210             :         static int idx;
     211             :         static int64_t seekpos, rowcount;
     212             :         static size_t len;
     213             :         static MapiHdl table_hdl;
     214             :         const char *name;
     215             :         char *buf;
     216             : 
     217             :         /* we pick our own portion of the linebuffer */
     218           0 :         text = rl_line_buffer + strlen(rl_line_buffer) - 1;
     219           0 :         while (text > rl_line_buffer && !isspace((unsigned char) *text))
     220           0 :                 text--;
     221           0 :         if (!state) {
     222           0 :                 idx = 0;
     223           0 :                 len = strlen(text);
     224             :         }
     225             : 
     226             : /*      printf("expand test:%s\n",text);
     227             :         printf("currentline:%s\n",rl_line_buffer); */
     228             : 
     229           0 :         while (mal_commands[idx] && (name = mal_commands[idx++])) {
     230           0 :                 if (strncasecmp(name, text, len) == 0)
     231           0 :                         return strdup(name);
     232             :         }
     233             :         /* try the server to answer */
     234           0 :         if (!state) {
     235             :                 char *c;
     236           0 :                 c = strstr(text, ":=");
     237           0 :                 if (c)
     238           0 :                         text = c + 2;
     239           0 :                 while (isspace((unsigned char) *text))
     240           0 :                         text++;
     241           0 :                 if ((buf = malloc(strlen(text) + 32)) == NULL)
     242             :                         return NULL;
     243           0 :                 if (strchr(text, '.') == NULL)
     244           0 :                         snprintf(buf, strlen(text) + 32,
     245             :                                  "manual.completion(\"%s.*(\");", text);
     246             :                 else
     247           0 :                         snprintf(buf, strlen(text) + 32,
     248             :                                  "manual.completion(\"%s(\");", text);
     249           0 :                 seekpos = 0;
     250           0 :                 table_hdl = mapi_query(_mid, buf);
     251           0 :                 free(buf);
     252           0 :                 if (table_hdl == NULL || mapi_error(_mid)) {
     253           0 :                         if (table_hdl) {
     254           0 :                                 mapi_explain_query(table_hdl, stderr);
     255           0 :                                 mapi_close_handle(table_hdl);
     256             :                         } else
     257           0 :                                 mapi_explain(_mid, stderr);
     258           0 :                         return NULL;
     259             :                 }
     260           0 :                 mapi_fetch_all_rows(table_hdl);
     261           0 :                 rowcount = mapi_get_row_count(table_hdl);
     262             :         }
     263             : 
     264           0 :         while (seekpos < rowcount) {
     265           0 :                 if (mapi_seek_row(table_hdl, seekpos++, MAPI_SEEK_SET) != MOK ||
     266           0 :                     mapi_fetch_row(table_hdl) <= 0)
     267           0 :                         continue;
     268           0 :                 name = mapi_fetch_field(table_hdl, 0);
     269           0 :                 if (name)
     270           0 :                         return strdup(name);
     271             :         }
     272             : 
     273             :         return NULL;
     274             : }
     275             : 
     276             : static char **
     277           0 : mal_completion(const char *text, int start, int end)
     278             : {
     279             :         (void) start;
     280             :         (void) end;
     281             : 
     282             :         /* FIXME: Nice, context-sensitive completion strategy should go here */
     283           0 :         return rl_completion_matches(text, mal_command_generator);
     284             : }
     285             : 
     286             : 
     287             : rl_completion_func_t *
     288           0 : suspend_completion(void)
     289             : {
     290           0 :         rl_completion_func_t *func = rl_attempted_completion_function;
     291             : 
     292           0 :         rl_attempted_completion_function = NULL;
     293           0 :         return func;
     294             : }
     295             : 
     296             : void
     297           0 : continue_completion(rl_completion_func_t * func)
     298             : {
     299           0 :         rl_attempted_completion_function = func;
     300           0 : }
     301             : 
     302             : static void
     303           0 : readline_show_error(const char *msg) {
     304           0 :         rl_save_prompt();
     305           0 :         rl_message(msg);
     306           0 :         rl_restore_prompt();
     307           0 :         rl_clear_message();
     308           0 : }
     309             : 
     310             : #ifndef BUFFER_SIZE
     311             : #define BUFFER_SIZE 1024
     312             : #endif
     313             : 
     314             : static int
     315           0 : invoke_editor(int cnt, int key) {
     316             :         char editor_command[BUFFER_SIZE];
     317             :         char *read_buff = NULL;
     318             :         char *editor = NULL;
     319             :         FILE *fp = NULL;
     320             :         long content_len;
     321             :         size_t read_bytes, idx;
     322             : 
     323             :         (void) cnt;
     324             :         (void) key;
     325             : 
     326             : #ifdef NATIVE_WIN32
     327             :         char *mytemp;
     328             :         char template[] = "mclient_temp_XXXXXX";
     329             :         if ((mytemp = _mktemp(template)) == NULL) {
     330             :                 readline_show_error("invoke_editor: Cannot create temp file\n");
     331             :                 goto bailout;
     332             :         }
     333             :         if ((fp = MT_fopen(mytemp, "r+")) == NULL) {
     334             :                 // Notify the user that we cannot create temp file
     335             :                 readline_show_error("invoke_editor: Cannot create temp file\n");
     336             :                 goto bailout;
     337             :         }
     338             : #else
     339             :         int mytemp;
     340           0 :         char template[] = "/tmp/mclient_temp_XXXXXX";
     341           0 :         mode_t msk = umask(077);
     342           0 :         mytemp = mkstemp(template);
     343           0 :         (void) umask(msk);
     344           0 :         if (mytemp == -1) {
     345           0 :                 readline_show_error("invoke_editor: Cannot create temp file\n");
     346           0 :                 goto bailout;
     347             :         }
     348           0 :         if ((fp = fdopen(mytemp, "r+")) == NULL) {
     349             :                 // Notify the user that we cannot create temp file
     350           0 :                 readline_show_error("invoke_editor: Cannot create temp file\n");
     351           0 :                 goto bailout;
     352             :         }
     353             : #endif
     354             : 
     355           0 :         fwrite(rl_line_buffer, sizeof(char), rl_end, fp);
     356           0 :         fflush(fp);
     357             : 
     358           0 :         editor = getenv("VISUAL");
     359           0 :         if (editor == NULL) {
     360           0 :                 editor = getenv("EDITOR");
     361           0 :                 if (editor == NULL) {
     362           0 :                         readline_show_error("invoke_editor: EDITOR/VISUAL env variable not set\n");
     363           0 :                         goto bailout;
     364             :                 }
     365             :         }
     366             : 
     367           0 :         snprintf(editor_command, BUFFER_SIZE, "%s %s", editor, template);
     368           0 :         if (system(editor_command) != 0) {
     369           0 :                 readline_show_error("invoke_editor: Starting editor failed\n");
     370           0 :                 goto bailout;
     371             :         }
     372             : 
     373           0 :         fseek(fp, 0L, SEEK_END);
     374           0 :         content_len = ftell(fp);
     375           0 :         rewind(fp);
     376             : 
     377           0 :         if (content_len > 0) {
     378           0 :                 read_buff = (char *)malloc(content_len + 1);
     379           0 :                 if (read_buff == NULL) {
     380           0 :                         readline_show_error("invoke_editor: Cannot allocate memory\n");
     381           0 :                         goto bailout;
     382             :                 }
     383             : 
     384           0 :                 read_bytes = fread(read_buff, sizeof(char), (size_t) content_len, fp);
     385           0 :                 if (read_bytes != (size_t) content_len) {
     386           0 :                         readline_show_error("invoke_editor: Did not read from file correctly\n");
     387           0 :                         goto bailout;
     388             :                 }
     389             : 
     390           0 :                 read_buff[read_bytes] = 0;
     391             : 
     392             :                 /* Remove trailing whitespace */
     393           0 :                 idx = read_bytes - 1;
     394           0 :                 while(isspace(*(read_buff + idx))) {
     395           0 :                         read_buff[idx] = 0;
     396           0 :                         idx--;
     397             :                 }
     398             : 
     399           0 :                 rl_replace_line(read_buff, 0);
     400           0 :                 rl_point = (int)(idx + 1);  // place the point one character after the end of the string
     401             : 
     402           0 :                 free(read_buff);
     403             :         } else {
     404           0 :                 rl_replace_line("", 0);
     405           0 :                 rl_point = 0;
     406             :         }
     407             : 
     408           0 :         fclose(fp);
     409             :         MT_remove(template);
     410             : 
     411           0 :         return 0;
     412             : 
     413           0 : bailout:
     414           0 :         if (fp)
     415           0 :                 fclose(fp);
     416           0 :         free(read_buff);
     417             :         MT_remove(template);
     418           0 :         return 1;
     419             : }
     420             : 
     421             : void
     422           0 : init_readline(Mapi mid, const char *lang, bool save_history)
     423             : {
     424           0 :         language = lang;
     425           0 :         _mid = mid;
     426             :         /* Allow conditional parsing of the ~/.inputrc file. */
     427           0 :         rl_readline_name = "MapiClient";
     428             :         /* Tell the completer that we want to try our own completion
     429             :          * before std completion (filename) kicks in. */
     430           0 :         if (strcmp(language, "sql") == 0) {
     431           0 :                 rl_attempted_completion_function = sql_completion;
     432           0 :         } else if (strcmp(language, "mal") == 0) {
     433             :                 /* recognize the help function, should react to <FCN2> */
     434             : #ifdef illegal_ESC_binding
     435             :                 rl_bind_key('\033', mal_help);
     436             : #endif
     437           0 :                 rl_attempted_completion_function = mal_completion;
     438             :         }
     439             : 
     440           0 :         rl_add_funmap_entry("invoke-editor", invoke_editor);
     441           0 :         rl_bind_keyseq("\\M-e", invoke_editor);
     442             : 
     443           0 :         if (save_history) {
     444             :                 int len;
     445           0 :                 if (getenv("HOME") != NULL) {
     446           0 :                         len = snprintf(_history_file, FILENAME_MAX,
     447             :                                  "%s/.mapiclient_history_%s",
     448             :                                  getenv("HOME"), language);
     449           0 :                         if (len == -1 || len >= FILENAME_MAX)
     450           0 :                                 fprintf(stderr, "Warning: history filename path is too large\n");
     451             :                         else
     452           0 :                                 _save_history = true;
     453             :                 }
     454           0 :                 if (_save_history) {
     455             :                         FILE *f;
     456           0 :                         switch (read_history(_history_file)) {
     457             :                         case 0:
     458             :                                 /* success */
     459             :                                 break;
     460             :                         case ENOENT:
     461             :                                 /* history file didn't exist, so try to create
     462             :                                  * it and then try again */
     463           0 :                                 if ((f = MT_fopen(_history_file, "w")) == NULL) {
     464             :                                         /* failed to create, don't
     465             :                                          * bother saving */
     466           0 :                                         _save_history = 0;
     467             :                                 } else {
     468           0 :                                         (void) fclose(f);
     469           0 :                                         if (read_history(_history_file) != 0) {
     470             :                                                 /* still no luck, don't
     471             :                                                  * bother saving */
     472           0 :                                                 _save_history = 0;
     473             :                                         }
     474             :                                 }
     475             :                                 break;
     476           0 :                         default:
     477             :                                 /* unrecognized failure, don't bother saving */
     478           0 :                                 _save_history = 0;
     479           0 :                                 break;
     480             :                         }
     481           0 :                 }
     482           0 :                 if (!_save_history)
     483           0 :                         fprintf(stderr, "Warning: not saving history\n");
     484             :         }
     485           0 : }
     486             : 
     487             : void
     488           0 : deinit_readline(void)
     489             : {
     490             :         /* nothing to do since we use append_history() */
     491           0 : }
     492             : 
     493             : void
     494           0 : save_line(const char *s)
     495             : {
     496           0 :         add_history(s);
     497           0 :         if (_save_history)
     498           0 :                 append_history(1, _history_file);
     499           0 : }
     500             : 
     501             : 
     502             : #endif /* HAVE_LIBREADLINE */

Generated by: LCOV version 1.14