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 | |
24 | static 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 | |
40 | static Mapi _mid; |
41 | static char _history_file[FILENAME_MAX]; |
42 | static int _save_history = 0; |
43 | static const char *language; |
44 | |
45 | static char * |
46 | sql_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) */ |
86 | static char * |
87 | sql_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 | |
112 | static char ** |
113 | sql_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 | |
138 | static 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 */ |
159 | static int |
160 | mal_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 | |
203 | static char * |
204 | mal_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 | |
277 | static char ** |
278 | mal_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 | |
288 | rl_completion_func_t * |
289 | suspend_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 | |
297 | void |
298 | continue_completion(rl_completion_func_t * func) |
299 | { |
300 | rl_attempted_completion_function = func; |
301 | } |
302 | |
303 | void |
304 | init_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 | |
366 | void |
367 | deinit_readline(void) |
368 | { |
369 | /* nothing to do since we use append_history() */ |
370 | } |
371 | |
372 | void |
373 | save_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 | |