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 - 2019 MonetDB B.V.
7 */
8
9/*
10 * Readline specific stuff
11 */
12#include "monetdb_config.h"
13
14#ifdef HAVE_LIBREADLINE
15
16#include <readline/readline.h>
17#include <readline/history.h>
18#include "ReadlineTools.h"
19
20#ifdef HAVE_STRINGS_H
21#include <strings.h> /* for strncasecmp */
22#endif
23
24static const char *sql_commands[] = {
25 "SELECT",
26 "INSERT",
27 "UPDATE",
28 "SET",
29 "DELETE",
30 "COMMIT",
31 "ROLLBACK",
32 "DROP TABLE",
33 "CREATE",
34 "ALTER",
35 "RELEASE SAVEPOINT",
36 "START TRANSACTION",
37 0,
38};
39
40static Mapi _mid;
41static char _history_file[FILENAME_MAX];
42static int _save_history = 0;
43static const char *language;
44
45static char *
46sql_tablename_generator(const char *text, int state)
47{
48
49 static int64_t seekpos, rowcount;
50 static size_t len;
51 static MapiHdl table_hdl;
52
53 if (!state) {
54 char *query;
55
56 seekpos = 0;
57 len = strlen(text);
58 if ((query = malloc(len + 150)) == NULL)
59 return NULL;
60 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);
61 table_hdl = mapi_query(_mid, query);
62 free(query);
63 if (table_hdl == NULL || mapi_error(_mid)) {
64 if (table_hdl) {
65 mapi_explain_query(table_hdl, stderr);
66 mapi_close_handle(table_hdl);
67 } else
68 mapi_explain(_mid, stderr);
69 return NULL;
70 }
71 mapi_fetch_all_rows(table_hdl);
72 rowcount = mapi_get_row_count(table_hdl);
73 }
74
75 while (seekpos < rowcount) {
76 if (mapi_seek_row(table_hdl, seekpos++, MAPI_SEEK_SET) != MOK ||
77 mapi_fetch_row(table_hdl) <= 0)
78 continue;
79 return strdup(mapi_fetch_field(table_hdl, 0));
80 }
81
82 return NULL;
83}
84
85/* SQL commands (at start of line) */
86static char *
87sql_command_generator(const char *text, int state)
88{
89
90 static int idx, len;
91 const char *name;
92
93 if (!state) {
94 idx = 0;
95 len = strlen(text);
96 }
97
98
99 while ((name = sql_commands[idx++])) {
100#ifdef HAVE_STRNCASECMP
101 if (strncasecmp(name, text, len) == 0)
102#else
103 if (strncmp(name, text, len) == 0)
104#endif
105 return strdup(name);
106 }
107
108 return NULL;
109}
110
111
112static char **
113sql_completion(const char *text, int start, int end)
114{
115 char **matches;
116
117 matches = (char **) NULL;
118
119 (void) end;
120
121 /* FIXME: Nice, context-sensitive completion strategy should go here */
122 if (strcmp(language, "sql") == 0) {
123 if (start == 0) {
124 matches = rl_completion_matches(text, sql_command_generator);
125 } else {
126 matches = rl_completion_matches(text, sql_tablename_generator);
127 }
128 }
129 if (strcmp(language, "mal") == 0) {
130 matches = rl_completion_matches(text, sql_tablename_generator);
131 }
132
133 return (matches);
134}
135
136/* The MAL completion help */
137
138static const char *mal_commands[] = {
139 "address",
140 "atom",
141 "barrier",
142 "catch",
143 "command",
144 "comment",
145 "exit",
146 "end",
147 "function",
148 "factory",
149 "leave",
150 "pattern",
151 "module",
152 "raise",
153 "redo",
154 0
155};
156
157#ifdef illegal_ESC_binding
158/* see also init_readline() below */
159static int
160mal_help(int cnt, int key)
161{
162 char *name, *c, *buf;
163 int64_t seekpos = 0, rowcount;
164 MapiHdl table_hdl;
165
166 (void) cnt;
167 (void) key;
168
169 c = rl_line_buffer + strlen(rl_line_buffer) - 1;
170 while (c > rl_line_buffer && isspace((unsigned char) *c))
171 c--;
172 while (c > rl_line_buffer && !isspace((unsigned char) *c))
173 c--;
174 if ((buf = malloc(strlen(c) + 20)) == NULL)
175 return 0;
176 snprintf(buf, strlen(c) + 20, "manual.help(\"%s\");", c);
177 table_hdl = mapi_query(_mid, buf);
178 free(buf);
179 if (table_hdl == NULL || mapi_error(_mid)) {
180 if (table_hdl) {
181 mapi_explain_query(table_hdl, stderr);
182 mapi_close_handle(table_hdl);
183 } else
184 mapi_explain(_mid, stderr);
185 return 0;
186 }
187 mapi_fetch_all_rows(table_hdl);
188 rowcount = mapi_get_row_count(table_hdl);
189
190 printf("\n");
191 while (seekpos < rowcount) {
192 if (mapi_seek_row(table_hdl, seekpos++, MAPI_SEEK_SET) != MOK ||
193 mapi_fetch_row(table_hdl) <= 0)
194 continue;
195 name = mapi_fetch_field(table_hdl, 0);
196 if (name)
197 printf("%s\n", name);
198 }
199 return key;
200}
201#endif
202
203static char *
204mal_command_generator(const char *text, int state)
205{
206
207 static int idx;
208 static int64_t seekpos, rowcount;
209 static size_t len;
210 static MapiHdl table_hdl;
211 const char *name;
212 char *buf;
213
214 /* we pick our own portion of the linebuffer */
215 text = rl_line_buffer + strlen(rl_line_buffer) - 1;
216 while (text > rl_line_buffer && !isspace((unsigned char) *text))
217 text--;
218 if (!state) {
219 idx = 0;
220 len = strlen(text);
221 }
222
223/* printf("expand test:%s\n",text);
224 printf("currentline:%s\n",rl_line_buffer); */
225
226 while (mal_commands[idx] && (name = mal_commands[idx++])) {
227#ifdef HAVE_STRNCASECMP
228 if (strncasecmp(name, text, len) == 0)
229#else
230 if (strncmp(name, text, len) == 0)
231#endif
232 return strdup(name);
233 }
234 /* try the server to answer */
235 if (!state) {
236 char *c;
237 c = strstr(text, ":=");
238 if (c)
239 text = c + 2;
240 while (isspace((unsigned char) *text))
241 text++;
242 if ((buf = malloc(strlen(text) + 32)) == NULL)
243 return NULL;
244 if (strchr(text, '.') == NULL)
245 snprintf(buf, strlen(text) + 32,
246 "manual.completion(\"%s.*(\");", text);
247 else
248 snprintf(buf, strlen(text) + 32,
249 "manual.completion(\"%s(\");", text);
250 seekpos = 0;
251 table_hdl = mapi_query(_mid, buf);
252 free(buf);
253 if (table_hdl == NULL || mapi_error(_mid)) {
254 if (table_hdl) {
255 mapi_explain_query(table_hdl, stderr);
256 mapi_close_handle(table_hdl);
257 } else
258 mapi_explain(_mid, stderr);
259 return NULL;
260 }
261 mapi_fetch_all_rows(table_hdl);
262 rowcount = mapi_get_row_count(table_hdl);
263 }
264
265 while (seekpos < rowcount) {
266 if (mapi_seek_row(table_hdl, seekpos++, MAPI_SEEK_SET) != MOK ||
267 mapi_fetch_row(table_hdl) <= 0)
268 continue;
269 name = mapi_fetch_field(table_hdl, 0);
270 if (name)
271 return strdup(name);
272 }
273
274 return NULL;
275}
276
277static char **
278mal_completion(const char *text, int start, int end)
279{
280 (void) start;
281 (void) end;
282
283 /* FIXME: Nice, context-sensitive completion strategy should go here */
284 return rl_completion_matches(text, mal_command_generator);
285}
286
287
288rl_completion_func_t *
289suspend_completion(void)
290{
291 rl_completion_func_t *func = rl_attempted_completion_function;
292
293 rl_attempted_completion_function = NULL;
294 return func;
295}
296
297void
298continue_completion(rl_completion_func_t * func)
299{
300 rl_attempted_completion_function = func;
301}
302
303void
304init_readline(Mapi mid, const char *lang, int save_history)
305{
306 language = lang;
307 _mid = mid;
308 /* Allow conditional parsing of the ~/.inputrc file. */
309 rl_readline_name = "MapiClient";
310 /* Tell the completer that we want to try our own completion
311 * before std completion (filename) kicks in. */
312 if (strcmp(language, "sql") == 0) {
313 rl_attempted_completion_function = sql_completion;
314 } else if (strcmp(language, "mal") == 0) {
315 /* recognize the help function, should react to <FCN2> */
316#ifdef illegal_ESC_binding
317 rl_bind_key('\033', mal_help);
318#endif
319 rl_attempted_completion_function = mal_completion;
320 }
321
322 if (save_history) {
323 int len;
324 if (getenv("HOME") != NULL) {
325 len = snprintf(_history_file, FILENAME_MAX,
326 "%s/.mapiclient_history_%s",
327 getenv("HOME"), language);
328 if (len == -1 || len >= FILENAME_MAX)
329 fprintf(stderr, "Warning: history filename path is too large\n");
330 else
331 _save_history = 1;
332 }
333 if (_save_history) {
334 FILE *f;
335 switch (read_history(_history_file)) {
336 case 0:
337 /* success */
338 break;
339 case ENOENT:
340 /* history file didn't exist, so try to create
341 * it and then try again */
342 if ((f = fopen(_history_file, "w")) == NULL) {
343 /* failed to create, don't
344 * bother saving */
345 _save_history = 0;
346 } else {
347 (void) fclose(f);
348 if (read_history(_history_file) != 0) {
349 /* still no luck, don't
350 * bother saving */
351 _save_history = 0;
352 }
353 }
354 break;
355 default:
356 /* unrecognized failure, don't bother saving */
357 _save_history = 0;
358 break;
359 }
360 }
361 if (!_save_history)
362 fprintf(stderr, "Warning: not saving history\n");
363 }
364}
365
366void
367deinit_readline(void)
368{
369 /* nothing to do since we use append_history() */
370}
371
372void
373save_line(const char *s)
374{
375 add_history(s);
376 if (_save_history)
377 append_history(1, _history_file);
378}
379
380
381#endif /* HAVE_LIBREADLINE */
382