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 | /* Author(s) M.L. Kersten |
10 | * Module import |
11 | * The import statement simple switches the parser to a new input file, which |
12 | * takes precedence. The context for which the file should be interpreted |
13 | * is determined by the module name supplied. |
14 | * Typically this involves a module, whose definitions are stored at |
15 | * a known location. |
16 | * The import context is located. If the module already exists, |
17 | * we should silently skip parsing the file. This is handled at the parser level. |
18 | * The files are extracted from a default location, |
19 | * namely the DBHOME/modules directory. |
20 | * |
21 | * If the string starts with '/' or '~' the context is not changed. |
22 | * |
23 | * Every IMPORT statement denotes a possible dynamic load library. |
24 | * Make sure it is loaded as well. |
25 | */ |
26 | |
27 | #include "monetdb_config.h" |
28 | #include "mal_import.h" |
29 | #include "mal_interpreter.h" /* for showErrors() */ |
30 | #include "mal_linker.h" /* for loadModuleLibrary() */ |
31 | #include "mal_scenario.h" |
32 | #include "mal_parser.h" |
33 | #include "mal_private.h" |
34 | |
35 | void |
36 | slash_2_dir_sep(str fname) |
37 | { |
38 | char *s; |
39 | |
40 | for (s = fname; *s; s++) |
41 | if (*s == '/') |
42 | *s = DIR_SEP; |
43 | } |
44 | |
45 | static str |
46 | malResolveFile(const char *fname) |
47 | { |
48 | char path[FILENAME_MAX]; |
49 | str script; |
50 | int written; |
51 | |
52 | written = snprintf(path, FILENAME_MAX, "%s" , fname); |
53 | if (written == -1 || written >= FILENAME_MAX) |
54 | return NULL; |
55 | slash_2_dir_sep(path); |
56 | if ((script = MSP_locate_script(path)) == NULL) { |
57 | /* this function is also called for scripts that are not located |
58 | * in the modpath, so if we can't find it, just default to |
59 | * whatever was given, as it can be in current dir, or an |
60 | * absolute location to somewhere */ |
61 | script = GDKstrdup(fname); |
62 | } |
63 | return script; |
64 | } |
65 | |
66 | static stream * |
67 | malOpenSource(str file) |
68 | { |
69 | stream *fd = NULL; |
70 | |
71 | if (file) |
72 | fd = open_rastream(file); |
73 | return fd; |
74 | } |
75 | |
76 | #ifndef HAVE_EMBEDDED |
77 | /* |
78 | * The malLoadScript routine merely reads the contents of a file into |
79 | * the input buffer of the client. It is typically used in situations |
80 | * where an intermediate file is used to pass commands around. |
81 | * Since the parser needs access to the complete block, we first have |
82 | * to find out how long the input is. |
83 | */ |
84 | static str |
85 | malLoadScript(str name, bstream **fdin) |
86 | { |
87 | stream *fd; |
88 | size_t sz; |
89 | |
90 | fd = malOpenSource(name); |
91 | if (fd == NULL || mnstr_errnr(fd) == MNSTR_OPEN_ERROR) { |
92 | close_stream(fd); |
93 | throw(MAL, "malInclude" , "could not open file: %s" , name); |
94 | } |
95 | sz = getFileSize(fd); |
96 | if (sz > (size_t) 1 << 29) { |
97 | close_stream(fd); |
98 | throw(MAL, "malInclude" , "file %s too large to process" , name); |
99 | } |
100 | *fdin = bstream_create(fd, sz == 0 ? (size_t) (2 * 128 * BLOCK) : sz); |
101 | if(*fdin == NULL) { |
102 | close_stream(fd); |
103 | throw(MAL, "malInclude" , MAL_MALLOC_FAIL); |
104 | } |
105 | if (bstream_next(*fdin) < 0) { |
106 | bstream_destroy(*fdin); |
107 | *fdin = NULL; |
108 | throw(MAL, "malInclude" , "could not read %s" , name); |
109 | } |
110 | return MAL_SUCCEED; |
111 | } |
112 | #endif |
113 | |
114 | /* |
115 | * Beware that we have to isolate the execution of the source file |
116 | * in its own environment. E.g. we have to remove the execution |
117 | * state until we are finished. |
118 | * The script being read may contain errors, such as non-balanced |
119 | * brackets as indicated by blkmode. |
120 | * It should be reset before continuing. |
121 | */ |
122 | #define restoreClient1 \ |
123 | if (c->fdin) \ |
124 | bstream_destroy(c->fdin); \ |
125 | c->fdin = oldfdin; \ |
126 | c->yycur = oldyycur; \ |
127 | c->listing = oldlisting; \ |
128 | c->mode = oldmode; \ |
129 | c->blkmode = oldblkmode; \ |
130 | c->bak = oldbak; \ |
131 | c->srcFile = oldsrcFile; \ |
132 | if(c->prompt) GDKfree(c->prompt); \ |
133 | c->prompt = oldprompt; \ |
134 | c->promptlength = strlen(c->prompt); |
135 | #define restoreClient2 \ |
136 | assert(c->glb == 0 || c->glb == oldglb); /* detect leak */ \ |
137 | c->glb = oldglb; \ |
138 | c->usermodule = oldusermodule; \ |
139 | c->curmodule = oldcurmodule;; \ |
140 | c->curprg = oldprg; |
141 | #define restoreClient \ |
142 | restoreClient1 \ |
143 | restoreClient2 |
144 | |
145 | #ifdef HAVE_EMBEDDED |
146 | extern char* mal_init_inline; |
147 | #endif |
148 | /* |
149 | * The include operation parses the file indentified and |
150 | * leaves the MAL code behind in the 'main' function. |
151 | */ |
152 | str |
153 | malInclude(Client c, str name, int listing) |
154 | { |
155 | str msg = MAL_SUCCEED; |
156 | str filename; |
157 | str p; |
158 | |
159 | bstream *oldfdin = c->fdin; |
160 | size_t oldyycur = c->yycur; |
161 | int oldlisting = c->listing; |
162 | enum clientmode oldmode = c->mode; |
163 | int oldblkmode = c->blkmode; |
164 | ClientInput *oldbak = c->bak; |
165 | str oldprompt = c->prompt; |
166 | str oldsrcFile = c->srcFile; |
167 | |
168 | MalStkPtr oldglb = c->glb; |
169 | Module oldusermodule = c->usermodule; |
170 | Module oldcurmodule = c->curmodule; |
171 | Symbol oldprg = c->curprg; |
172 | |
173 | c->prompt = GDKstrdup("" ); /* do not produce visible prompts */ |
174 | c->promptlength = 0; |
175 | c->listing = listing; |
176 | c->fdin = NULL; |
177 | |
178 | #ifdef HAVE_EMBEDDED |
179 | (void) filename; |
180 | (void) p; |
181 | { |
182 | size_t mal_init_len = strlen(mal_init_inline); |
183 | buffer* mal_init_buf; |
184 | stream* mal_init_stream; |
185 | |
186 | if ((mal_init_buf = GDKmalloc(sizeof(buffer))) == NULL) |
187 | throw(MAL, "malInclude" , MAL_MALLOC_FAIL); |
188 | if ((mal_init_stream = buffer_rastream(mal_init_buf, name)) == NULL) { |
189 | GDKfree(mal_init_buf); |
190 | throw(MAL, "malInclude" , MAL_MALLOC_FAIL); |
191 | } |
192 | buffer_init(mal_init_buf, mal_init_inline, mal_init_len); |
193 | c->srcFile = name; |
194 | c->yycur = 0; |
195 | c->bak = NULL; |
196 | if ((c->fdin = bstream_create(mal_init_stream, mal_init_len)) == NULL) { |
197 | mnstr_destroy(mal_init_stream); |
198 | GDKfree(mal_init_buf); |
199 | throw(MAL, "malInclude" , MAL_MALLOC_FAIL); |
200 | } |
201 | bstream_next(c->fdin); |
202 | parseMAL(c, c->curprg, 1, INT_MAX); |
203 | free(mal_init_buf); |
204 | free(mal_init_stream); |
205 | free(c->fdin); |
206 | c->fdin = NULL; |
207 | GDKfree(mal_init_buf); |
208 | } |
209 | #else |
210 | if ((filename = malResolveFile(name)) != NULL) { |
211 | name = filename; |
212 | do { |
213 | p = strchr(filename, PATH_SEP); |
214 | if (p) |
215 | *p = '\0'; |
216 | c->srcFile = filename; |
217 | c->yycur = 0; |
218 | c->bak = NULL; |
219 | if ((msg = malLoadScript(filename, &c->fdin)) == MAL_SUCCEED) { |
220 | parseMAL(c, c->curprg, 1, INT_MAX); |
221 | bstream_destroy(c->fdin); |
222 | } else { |
223 | /* TODO output msg ? */ |
224 | freeException(msg); |
225 | msg = MAL_SUCCEED; |
226 | } |
227 | if (p) |
228 | filename = p + 1; |
229 | } while (p); |
230 | GDKfree(name); |
231 | c->fdin = NULL; |
232 | } |
233 | #endif |
234 | restoreClient; |
235 | return msg; |
236 | } |
237 | |
238 | /*File and input processing |
239 | * A recurring situation is to execute a stream of simple MAL instructions |
240 | * stored on a file or comes from standard input. We parse one MAL |
241 | * instruction line at a time and attempt to execute it immediately. |
242 | * Note, this precludes entering complex MAL structures on the primary |
243 | * input channel, because 1) this requires complex code to keep track |
244 | * that we are in 'definition mode' 2) this requires (too) careful |
245 | * typing by the user, because he cannot make a typing error |
246 | * |
247 | * Therefore, all compound code fragments should be loaded and executed |
248 | * using the evalFile and callString command. It will parse the complete |
249 | * file into a MAL program block and execute it. |
250 | * |
251 | * Running looks much like an Import operation, except for the execution |
252 | * phase. This is performed in the context of an a priori defined |
253 | * stack frame. Life becomes a little complicated when the script contains |
254 | * a definition. |
255 | */ |
256 | str |
257 | evalFile(str fname, int listing) |
258 | { |
259 | Client c; |
260 | stream *fd; |
261 | str filename; |
262 | str msg = MAL_SUCCEED; |
263 | |
264 | filename = malResolveFile(fname); |
265 | if (filename == NULL) |
266 | throw(MAL, "mal.eval" ,"could not open file: %s\n" , fname); |
267 | fd = malOpenSource(filename); |
268 | GDKfree(filename); |
269 | if (fd == 0 || mnstr_errnr(fd) == MNSTR_OPEN_ERROR) { |
270 | if (fd) |
271 | close_stream(fd); |
272 | throw(MAL,"mal.eval" , "WARNING: could not open file '%s'\n" , fname); |
273 | } |
274 | |
275 | c= MCinitClient((oid)0, bstream_create(fd, 128 * BLOCK),0); |
276 | if( c == NULL){ |
277 | throw(MAL,"mal.eval" ,"Can not create user context" ); |
278 | } |
279 | c->curmodule = c->usermodule = userModule(); |
280 | if(c->curmodule == NULL) { |
281 | MCcloseClient(c); |
282 | throw(MAL,"mal.eval" ,SQLSTATE(HY001) MAL_MALLOC_FAIL); |
283 | } |
284 | c->promptlength = 0; |
285 | c->listing = listing; |
286 | |
287 | if ( (msg = defaultScenario(c)) ) { |
288 | MCcloseClient(c); |
289 | return msg; |
290 | } |
291 | if((msg = MSinitClientPrg(c, "user" , "main" )) != MAL_SUCCEED) { |
292 | MCcloseClient(c); |
293 | return msg; |
294 | } |
295 | |
296 | msg = runScenario(c,0); |
297 | MCcloseClient(c); |
298 | return msg; |
299 | } |
300 | |
301 | /* patch a newline character if needed */ |
302 | static str |
303 | mal_cmdline(char *s, size_t *len) |
304 | { |
305 | if (*len && s[*len - 1] != '\n') { |
306 | char *n = GDKmalloc(*len + 2); |
307 | if (n == NULL) |
308 | return s; |
309 | memcpy(n, s, *len); |
310 | n[*len] = '\n'; |
311 | n[*len + 1] = 0; |
312 | (*len)++; |
313 | return n; |
314 | } |
315 | return s; |
316 | } |
317 | |
318 | str |
319 | compileString(Symbol *fcn, Client cntxt, str s) |
320 | { |
321 | Client c; |
322 | size_t len = strlen(s); |
323 | buffer *b; |
324 | str msg = MAL_SUCCEED; |
325 | str qry; |
326 | str old = s; |
327 | stream *bs; |
328 | bstream *fdin = NULL; |
329 | |
330 | s = mal_cmdline(s, &len); |
331 | qry = s; |
332 | if (old == s) { |
333 | qry = GDKstrdup(s); |
334 | if(!qry) |
335 | throw(MAL,"mal.eval" ,SQLSTATE(HY001) MAL_MALLOC_FAIL); |
336 | } |
337 | |
338 | mal_unquote(qry); |
339 | b = (buffer *) GDKzalloc(sizeof(buffer)); |
340 | if (b == NULL) { |
341 | GDKfree(qry); |
342 | throw(MAL,"mal.eval" ,SQLSTATE(HY001) MAL_MALLOC_FAIL); |
343 | } |
344 | |
345 | buffer_init(b, qry, len); |
346 | bs = buffer_rastream(b, "compileString" ); |
347 | if (bs == NULL) { |
348 | GDKfree(qry); |
349 | GDKfree(b); |
350 | throw(MAL,"mal.eval" ,SQLSTATE(HY001) MAL_MALLOC_FAIL); |
351 | } |
352 | fdin = bstream_create(bs, b->len); |
353 | if (fdin == NULL) { |
354 | GDKfree(qry); |
355 | GDKfree(b); |
356 | throw(MAL,"mal.eval" ,SQLSTATE(HY001) MAL_MALLOC_FAIL); |
357 | } |
358 | strncpy(fdin->buf, qry, len+1); |
359 | |
360 | // compile in context of called for |
361 | c= MCinitClient((oid)0, fdin, 0); |
362 | if( c == NULL){ |
363 | GDKfree(qry); |
364 | GDKfree(b); |
365 | throw(MAL,"mal.eval" ,"Can not create user context" ); |
366 | } |
367 | c->curmodule = c->usermodule = cntxt->usermodule; |
368 | c->promptlength = 0; |
369 | c->listing = 0; |
370 | |
371 | if ( (msg = defaultScenario(c)) ) { |
372 | GDKfree(qry); |
373 | GDKfree(b); |
374 | c->usermodule= 0; |
375 | MCcloseClient(c); |
376 | return msg; |
377 | } |
378 | |
379 | msg = MSinitClientPrg(c, "user" , "main" );/* create new context */ |
380 | if(msg == MAL_SUCCEED && c->phase[MAL_SCENARIO_PARSER]) |
381 | msg = (str) (*c->phase[MAL_SCENARIO_PARSER])(c); |
382 | if(msg == MAL_SUCCEED && c->phase[MAL_SCENARIO_OPTIMIZE]) |
383 | msg = (str) (*c->phase[MAL_SCENARIO_OPTIMIZE])(c); |
384 | |
385 | *fcn = c->curprg; |
386 | c->curprg = 0; |
387 | c->usermodule= 0; |
388 | /* restore IO channel */ |
389 | MCcloseClient(c); |
390 | GDKfree(qry); |
391 | GDKfree(b); |
392 | return msg; |
393 | } |
394 | |
395 | str |
396 | callString(Client cntxt, str s, int listing) |
397 | { |
398 | Client c; |
399 | int i; |
400 | size_t len = strlen(s); |
401 | buffer *b; |
402 | str old =s; |
403 | str msg = MAL_SUCCEED, qry; |
404 | bstream *bs; |
405 | |
406 | s = mal_cmdline(s, &len); |
407 | qry = s; |
408 | if (old == s) { |
409 | qry = GDKstrdup(s); |
410 | if(!qry) |
411 | throw(MAL,"callstring" , SQLSTATE(HY001) MAL_MALLOC_FAIL); |
412 | } |
413 | |
414 | mal_unquote(qry); |
415 | b = (buffer *) GDKzalloc(sizeof(buffer)); |
416 | if (b == NULL){ |
417 | GDKfree(qry); |
418 | throw(MAL,"callstring" , SQLSTATE(HY001) MAL_MALLOC_FAIL); |
419 | } |
420 | |
421 | buffer_init(b, qry, len); |
422 | bs = bstream_create(buffer_rastream(b, "callString" ), b->len); |
423 | if (bs == NULL){ |
424 | GDKfree(b); |
425 | GDKfree(qry); |
426 | throw(MAL,"callstring" , SQLSTATE(HY001) MAL_MALLOC_FAIL); |
427 | } |
428 | c= MCinitClient((oid)0, bs,0); |
429 | if( c == NULL){ |
430 | GDKfree(b); |
431 | GDKfree(qry); |
432 | throw(MAL,"mal.call" ,"Can not create user context" ); |
433 | } |
434 | strncpy(c->fdin->buf, qry, len+1); |
435 | c->curmodule = c->usermodule = cntxt->usermodule; |
436 | c->promptlength = 0; |
437 | c->listing = listing; |
438 | |
439 | if ( (msg = defaultScenario(c)) ) { |
440 | c->usermodule = 0; |
441 | GDKfree(b); |
442 | GDKfree(qry); |
443 | MCcloseClient(c); |
444 | return msg; |
445 | } |
446 | |
447 | if((msg = MSinitClientPrg(c, "user" , "main" )) != MAL_SUCCEED) {/* create new context */ |
448 | c->usermodule = 0; |
449 | GDKfree(b); |
450 | GDKfree(qry); |
451 | MCcloseClient(c); |
452 | return msg; |
453 | } |
454 | if((msg = runScenario(c,1)) != MAL_SUCCEED) { |
455 | c->usermodule = 0; |
456 | GDKfree(b); |
457 | GDKfree(qry); |
458 | MCcloseClient(c); |
459 | return msg; |
460 | } |
461 | // The command may have changed the environment of the calling client. |
462 | // These settings should be propagated for further use. |
463 | //if( msg == MAL_SUCCEED){ |
464 | cntxt->scenario = c->scenario; |
465 | c->scenario = 0; |
466 | cntxt->sqlcontext = c->sqlcontext; |
467 | c->sqlcontext = 0; |
468 | for(i=1; i< SCENARIO_PROPERTIES; i++){ |
469 | cntxt->state[i] = c->state[i]; |
470 | c->state[i] = 0; |
471 | cntxt->phase[i] = c->phase[i]; |
472 | c->phase[i] = 0; |
473 | } |
474 | if(msg == MAL_SUCCEED && cntxt->phase[0] != c->phase[0]){ |
475 | cntxt->phase[0] = c->phase[0]; |
476 | cntxt->state[0] = c->state[0]; |
477 | msg = (str) (*cntxt->phase[0])(cntxt); // force re-initialize client context |
478 | } |
479 | //} |
480 | c->usermodule = 0; // keep it around |
481 | bstream_destroy(c->fdin); |
482 | c->fdin = 0; |
483 | MCcloseClient(c); |
484 | GDKfree(qry); |
485 | GDKfree(b); |
486 | return msg; |
487 | } |
488 | |