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

Generated by: LCOV version 1.14