LCOV - code coverage report
Current view: top level - clients/mapiclient - ReadlineTools.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 0 220 0.0 %
Date: 2024-12-19 23:10:26 Functions: 0 14 0.0 %

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

Generated by: LCOV version 1.14