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

Generated by: LCOV version 1.14