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
35void
36slash_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
45static str
46malResolveFile(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
66static stream *
67malOpenSource(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*/
84static str
85malLoadScript(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
146extern 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 */
152str
153malInclude(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 */
256str
257evalFile(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 */
302static str
303mal_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
318str
319compileString(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
395str
396callString(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