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 : /* (author) M.L. Kersten
14 : */
15 : #include "monetdb_config.h"
16 : #include "mal_scenario.h"
17 : #include "mal_session.h"
18 : #include "mal_instruction.h" /* for pushEndInstruction() */
19 : #include "mal_interpreter.h" /* for runMAL(), garbageElement() */
20 : #include "mal_parser.h" /* for parseMAL() */
21 : #include "mal_namespace.h"
22 : #include "mal_authorize.h"
23 : #include "mal_builder.h"
24 : #include "msabaoth.h"
25 : #include "mal_private.h"
26 : #include "mal_internal.h"
27 : #include "gdk.h" /* for opendir and friends */
28 :
29 : /*
30 : * The MonetDB server uses a startup script to boot the system.
31 : * This script is an ordinary MAL program, but will mostly
32 : * consist of include statements to load modules of general interest.
33 : * The startup script is run as user Admin.
34 : */
35 : str
36 314 : malBootstrap(char *modules[], bool embedded, const char *initpasswd)
37 : {
38 314 : Client c;
39 314 : str msg = MAL_SUCCEED;
40 :
41 314 : c = MCinitClient(MAL_ADMIN, NULL, NULL);
42 314 : if (c == NULL) {
43 0 : throw(MAL, "malBootstrap", "Failed to initialize client");
44 : }
45 314 : MT_thread_set_qry_ctx(NULL);
46 314 : assert(c != NULL);
47 314 : c->curmodule = c->usermodule = userModule();
48 314 : if (c->usermodule == NULL) {
49 0 : MCcloseClient(c);
50 0 : throw(MAL, "malBootstrap", "Failed to initialize client MAL module");
51 : }
52 314 : if ((msg = defaultScenario(c))) {
53 0 : MCcloseClient(c);
54 0 : return msg;
55 : }
56 314 : if ((msg = MSinitClientPrg(c, "user", "main")) != MAL_SUCCEED) {
57 0 : MCcloseClient(c);
58 0 : return msg;
59 : }
60 :
61 314 : if (MCinitClientThread(c) < 0) {
62 0 : MCcloseClient(c);
63 0 : throw(MAL, "malBootstrap", "Failed to create client thread");
64 : }
65 314 : if ((msg = malIncludeModules(c, modules, 0, embedded, initpasswd)) != MAL_SUCCEED) {
66 0 : MCcloseClient(c);
67 0 : return msg;
68 : }
69 297 : MCcloseClient(c);
70 297 : return msg;
71 : }
72 :
73 : /*
74 : * Every client has a 'main' function to collect the statements. Once
75 : * the END instruction has been found, it is added to the symbol table
76 : * and a fresh container is being constructed. Note, this scheme makes
77 : * testing for recursive function calls a little more difficult.
78 : * Therefore, type checking should be performed afterwards.
79 : *
80 : * In interactive mode, the closing statement is never reached. The
81 : * 'main' procedure is typically cleaned between successive external
82 : * messages except for its variables, which are considered global. This
83 : * storage container is re-used when during the previous call nothing
84 : * was added. At the end of the session we have to garbage collect the
85 : * BATs introduced.
86 : */
87 : static str
88 262306 : MSresetClientPrg(Client cntxt, const char *mod, const char *fcn)
89 : {
90 262306 : MalBlkPtr mb;
91 262306 : InstrPtr p;
92 :
93 262306 : mb = cntxt->curprg->def;
94 262306 : mb->stop = 1;
95 262306 : mb->errors = MAL_SUCCEED;
96 262306 : p = mb->stmt[0];
97 :
98 262306 : p->gc = 0;
99 262306 : p->retc = 1;
100 262306 : p->argc = 1;
101 262306 : p->argv[0] = 0;
102 :
103 262306 : setModuleId(p, mod);
104 262306 : setFunctionId(p, fcn);
105 262306 : if (findVariable(mb, fcn) < 0)
106 262296 : if ((p->argv[0] = newVariable(mb, fcn, strlen(fcn), TYPE_void)) < 0)
107 0 : throw(MAL, "resetClientPrg", SQLSTATE(HY013) MAL_MALLOC_FAIL);
108 :
109 262306 : setVarType(mb, findVariable(mb, fcn), TYPE_void);
110 262306 : return MAL_SUCCEED;
111 : }
112 :
113 : /*
114 : * Create a new container block
115 : */
116 :
117 : str
118 309952 : MSinitClientPrg(Client cntxt, const char *mod, const char *nme)
119 : {
120 309952 : int idx;
121 :
122 309952 : if (cntxt->curprg && idcmp(nme, cntxt->curprg->name) == 0)
123 262306 : return MSresetClientPrg(cntxt, putName(mod), putName(nme));
124 47646 : cntxt->curprg = newFunction(putName(mod), putName(nme), FUNCTIONsymbol);
125 47640 : if (cntxt->curprg == 0)
126 0 : throw(MAL, "initClientPrg", SQLSTATE(HY013) MAL_MALLOC_FAIL);
127 47640 : if ((idx = findVariable(cntxt->curprg->def, "main")) >= 0)
128 37644 : setVarType(cntxt->curprg->def, idx, TYPE_void);
129 47641 : insertSymbol(cntxt->usermodule, cntxt->curprg);
130 :
131 47639 : if (cntxt->glb == NULL)
132 37664 : cntxt->glb = newGlobalStack(MAXGLOBALS + cntxt->curprg->def->vsize);
133 47646 : if (cntxt->glb == NULL)
134 0 : throw(MAL, "initClientPrg", SQLSTATE(HY013) MAL_MALLOC_FAIL);
135 47646 : assert(cntxt->curprg->def != NULL);
136 47646 : assert(cntxt->curprg->def->vtop > 0);
137 : return MAL_SUCCEED;
138 : }
139 :
140 : /*
141 : * The default method to interact with the database server is to connect
142 : * using a port number. The first line received should contain
143 : * authorization information, such as user name.
144 : *
145 : * The scheduleClient receives a challenge response consisting of
146 : * endian:user:password:lang:database:
147 : */
148 : static void
149 149 : exit_streams(bstream *fin, stream *fout)
150 : {
151 149 : if (fout && fout != GDKstdout) {
152 149 : mnstr_flush(fout, MNSTR_FLUSH_DATA);
153 149 : close_stream(fout);
154 : }
155 149 : if (fin)
156 149 : bstream_destroy(fin);
157 149 : }
158 :
159 : static const char mal_enableflag[] = "mal_for_all";
160 :
161 : static bool
162 1451 : is_exiting(void *data)
163 : {
164 1451 : (void) data;
165 1451 : return GDKexiting();
166 : }
167 :
168 : static str MSserveClient(Client cntxt);
169 :
170 :
171 : static inline void
172 12 : cleanUpScheduleClient(Client c, str *command, str *err)
173 : {
174 12 : if (c) {
175 12 : MCcloseClient(c);
176 : }
177 12 : if (command) {
178 3 : GDKfree(*command);
179 3 : *command = NULL;
180 : }
181 12 : if (err) {
182 12 : freeException(*err);
183 12 : *err = NULL;
184 : }
185 12 : }
186 :
187 :
188 : void
189 37465 : MSscheduleClient(str command, str peer, str challenge, bstream *fin, stream *fout,
190 : protocol_version protocol, size_t blocksize)
191 : {
192 37465 : char *user = command, *algo = NULL, *passwd = NULL, *lang = NULL,
193 37465 : *handshake_opts = NULL;
194 37465 : char *database = NULL, *s;
195 37465 : const char *dbname;
196 37465 : str msg = MAL_SUCCEED;
197 37465 : bool filetrans = false;
198 37465 : Client c;
199 :
200 37465 : MT_thread_set_qry_ctx(NULL);
201 :
202 : /* decode BIG/LIT:user:{cypher}passwordchal:lang:database: line */
203 :
204 : /* byte order */
205 37471 : s = strchr(user, ':');
206 37471 : if (s) {
207 37322 : *s = 0;
208 37322 : mnstr_set_bigendian(fin->s, strcmp(user, "BIG") == 0);
209 37307 : user = s + 1;
210 : } else {
211 149 : mnstr_printf(fout, "!incomplete challenge '%s'\n", user);
212 149 : exit_streams(fin, fout);
213 149 : GDKfree(command);
214 310 : return;
215 : }
216 :
217 : /* passwd */
218 37307 : s = strchr(user, ':');
219 37307 : if (s) {
220 37307 : *s = 0;
221 37307 : passwd = s + 1;
222 : /* decode algorithm, i.e. {plain}mypasswordchallenge */
223 37307 : if (*passwd != '{') {
224 0 : mnstr_printf(fout, "!invalid password entry\n");
225 0 : exit_streams(fin, fout);
226 0 : GDKfree(command);
227 0 : return;
228 : }
229 37307 : algo = passwd + 1;
230 37307 : s = strchr(algo, '}');
231 37307 : if (!s) {
232 0 : mnstr_printf(fout, "!invalid password entry\n");
233 0 : exit_streams(fin, fout);
234 0 : GDKfree(command);
235 0 : return;
236 : }
237 37307 : *s = 0;
238 37307 : passwd = s + 1;
239 : } else {
240 0 : mnstr_printf(fout, "!incomplete challenge '%s'\n", user);
241 0 : exit_streams(fin, fout);
242 0 : GDKfree(command);
243 0 : return;
244 : }
245 :
246 : /* lang */
247 37307 : s = strchr(passwd, ':');
248 37307 : if (s) {
249 37307 : *s = 0;
250 37307 : lang = s + 1;
251 : } else {
252 0 : mnstr_printf(fout, "!incomplete challenge, missing language\n");
253 0 : exit_streams(fin, fout);
254 0 : GDKfree(command);
255 0 : return;
256 : }
257 :
258 : /* database */
259 37307 : s = strchr(lang, ':');
260 37307 : if (s) {
261 37307 : *s = 0;
262 37307 : database = s + 1;
263 : /* we can have stuff following, make it void */
264 37307 : s = strchr(database, ':');
265 37307 : if (s)
266 37307 : *s++ = 0;
267 : }
268 :
269 37307 : if (s && strncmp(s, "FILETRANS:", 10) == 0) {
270 37000 : s += 10;
271 37000 : filetrans = true;
272 307 : } else if (s && s[0] == ':') {
273 0 : s += 1;
274 0 : filetrans = false;
275 : }
276 :
277 37307 : if (s && strchr(s, ':') != NULL) {
278 37000 : handshake_opts = s;
279 37000 : s = strchr(s, ':');
280 37000 : *s++ = '\0';
281 : }
282 37307 : dbname = GDKgetenv("gdk_dbname");
283 37337 : if (database != NULL && database[0] != '\0' &&
284 36322 : strcmp(database, dbname) != 0) {
285 0 : mnstr_printf(fout, "!request for database '%s', "
286 : "but this is database '%s', "
287 : "did you mean to connect to monetdbd instead?\n",
288 : database, dbname);
289 : /* flush the error to the client, and abort further execution */
290 0 : exit_streams(fin, fout);
291 0 : GDKfree(command);
292 0 : return;
293 : } else {
294 37337 : c = MCinitClient(0, fin, fout);
295 37337 : if (c == NULL) {
296 0 : if (MCshutdowninprogress())
297 0 : mnstr_printf(fout,
298 : "!system shutdown in progress, please try again later\n");
299 : else
300 0 : mnstr_printf(fout, "!maximum concurrent client limit reached "
301 : "(%d), please try again later\n", MAL_MAXCLIENTS);
302 0 : exit_streams(fin, fout);
303 0 : GDKfree(command);
304 0 : return;
305 : }
306 37337 : c->filetrans = filetrans;
307 37337 : c->handshake_options = handshake_opts ? strdup(handshake_opts) : NULL;
308 : /* move this back !! */
309 37337 : if (c->usermodule == 0) {
310 37337 : c->curmodule = c->usermodule = userModule();
311 37337 : if (c->curmodule == NULL) {
312 0 : mnstr_printf(fout, "!could not allocate space\n");
313 0 : cleanUpScheduleClient(c, &command, &msg);
314 0 : return;
315 : }
316 : }
317 :
318 37337 : if ((msg = setScenario(c, lang)) != NULL) {
319 0 : mnstr_printf(c->fdout, "!%s\n", msg);
320 0 : mnstr_flush(c->fdout, MNSTR_FLUSH_DATA);
321 0 : cleanUpScheduleClient(c, &command, &msg);
322 0 : return;
323 : }
324 37337 : if (!GDKgetenv_isyes(mal_enableflag)
325 37337 : && strncasecmp("sql", lang, 3) != 0
326 502 : && strcmp(user, "monetdb") != 0) {
327 3 : mnstr_printf(fout,
328 : "!only the 'monetdb' user can use non-sql languages. "
329 : "run mserver5 with --set %s=yes to change this.\n",
330 : mal_enableflag);
331 3 : cleanUpScheduleClient(c, &command, &msg);
332 3 : return;
333 : }
334 : }
335 :
336 37334 : if ((msg = MSinitClientPrg(c, "user", "main")) != MAL_SUCCEED) {
337 0 : mnstr_printf(fout, "!could not allocate space\n");
338 0 : cleanUpScheduleClient(c, &command, &msg);
339 0 : return;
340 : }
341 :
342 : // at this point username should have being verified
343 37334 : c->username = GDKstrdup(user);
344 37330 : if (peer)
345 37330 : c->peer = GDKstrdup(peer);
346 :
347 : /* NOTE ABOUT STARTING NEW THREADS
348 : * At this point we have conducted experiments (Jun 2012) with
349 : * reusing threads. The implementation used was a lockless array of
350 : * semaphores to wake up threads to do work. Experimentation on
351 : * Linux, Solaris and Darwin showed no significant improvements, in
352 : * most cases no improvements at all. Hence the following
353 : * conclusion: thread reuse doesn't save up on the costs of just
354 : * forking new threads. Since the latter means no difficulties of
355 : * properly maintaining a pool of threads and picking the workers
356 : * out of them, it is favourable just to start new threads on
357 : * demand. */
358 :
359 : /* fork a new thread to handle this client */
360 :
361 37334 : c->protocol = protocol;
362 37334 : c->blocksize = blocksize;
363 :
364 37334 : if (c->initClient) {
365 37334 : if ((msg = c->initClient(c, passwd, challenge, algo)) != MAL_SUCCEED) {
366 9 : mnstr_printf(fout, "!%s\n", msg);
367 9 : GDKfree(command);
368 9 : if (c->exitClient)
369 9 : c->exitClient(c);
370 9 : cleanUpScheduleClient(c, NULL, &msg);
371 9 : return;
372 : }
373 : }
374 37324 : GDKfree(command);
375 :
376 37323 : mnstr_settimeout(c->fdin->s, 50, is_exiting, NULL);
377 37321 : msg = MSserveClient(c);
378 37325 : if (msg != MAL_SUCCEED) {
379 0 : freeException(msg);
380 : }
381 : }
382 :
383 : /*
384 : * After the client initialization has been finished, we can start the
385 : * interaction protocol. This involves parsing the input in the context
386 : * of an already defined procedure and upon success, its execution.
387 : *
388 : * In essence, this calls for an incremental parsing operation, because
389 : * we should wait until a complete basic block has been detected. Test,
390 : * first collect the instructions before we take them all.
391 : *
392 : * In interactive mode, we should remove the instructions before
393 : * accepting new ones. The function signature remains the same and the
394 : * symbol table should also not be affected. Aside from removing
395 : * instruction, we should also condense the variable stack, i.e.
396 : * removing at least the temporary variables, but maybe everything
397 : * beyond a previous defined point.
398 : *
399 : * Beware that we have to cleanup the global stack as well. This to
400 : * avoid subsequent calls to find garbage information. However, this
401 : * action is only required after a successful execution. Otherwise,
402 : * garbage collection is not needed.
403 : */
404 : void
405 304314 : MSresetInstructions(MalBlkPtr mb, int start)
406 : {
407 304314 : int i;
408 304314 : InstrPtr p;
409 :
410 74481621 : for (i = start; i < mb->ssize; i++) {
411 74177078 : p = getInstrPtr(mb, i);
412 74177078 : if (p)
413 37281 : freeInstruction(p);
414 74177307 : mb->stmt[i] = NULL;
415 : }
416 304543 : mb->stop = start;
417 304543 : }
418 :
419 : /*
420 : * MAL instructions generate variables.
421 : * The values of temporary variables should be cleaned at the end of a call
422 : * The values of global variables are retained.
423 : * Global variables should not start with C_ or X_
424 : */
425 : void
426 9973 : MSresetStack(Client cntxt, MalBlkPtr mb, MalStkPtr glb)
427 : {
428 9973 : InstrPtr sig = getInstrPtr(mb, 0);
429 9973 : int i, k = sig->argc;
430 :
431 9973 : if (mb->errors == MAL_SUCCEED) {
432 1251845 : for (i = sig->argc; i < mb->vtop; i++) {
433 1241872 : if (glb && i < glb->stktop && isTmpVar(mb, i) && !glb->keepTmps) {
434 7494 : if (mb->var[i].name)
435 2 : GDKfree(mb->var[i].name);
436 : /* clean stack entry */
437 7494 : garbageElement(cntxt, &glb->stk[i]);
438 7494 : glb->stk[i].vtype = TYPE_int;
439 7494 : glb->stk[i].len = 0;
440 7494 : glb->stk[i].val.pval = 0;
441 7494 : if (isVarConstant(mb, i))
442 4827 : garbageElement(cntxt, &mb->var[i].value);
443 : } else {
444 : /* compress the global variable list and stack */
445 1234378 : mb->var[k] = mb->var[i];
446 1234378 : glb->stk[k] = glb->stk[i];
447 1234378 : setVarUsed(mb, k);
448 1234378 : setVarInit(mb, k);
449 1234378 : if (i != k) {
450 68 : glb->stk[i].vtype = TYPE_int;
451 68 : glb->stk[i].len = 0;
452 68 : glb->stk[i].val.pval = 0;
453 68 : clrVarConstant(mb, i);
454 68 : clrVarCleanup(mb, i);
455 : }
456 1234378 : k++;
457 : }
458 : }
459 : }
460 9973 : assert(k <= mb->vsize);
461 9973 : mb->vtop = k;
462 9973 : }
463 :
464 : /* The symbol table be become filled with constant values to be garbage collected
465 : * The signature is always left behind.
466 : */
467 :
468 : void
469 0 : MSresetVariables(MalBlkPtr mb)
470 : {
471 0 : InstrPtr sig = getInstrPtr(mb, 0);
472 0 : int i;
473 :
474 0 : if (mb->errors == MAL_SUCCEED)
475 0 : for (i = sig->argc; i < mb->vtop; i++)
476 0 : if (isVarConstant(mb, i)) {
477 0 : VALclear(&getVarConstant(mb, i));
478 0 : clrVarConstant(mb, i);
479 : }
480 0 : }
481 :
482 : /*
483 : * Here we start the client. We need to initialize and allocate space
484 : * for the global variables. Thereafter it is up to the scenario
485 : * interpreter to process input.
486 : */
487 : static str
488 37316 : MSserveClient(Client c)
489 : {
490 37316 : MalBlkPtr mb;
491 37316 : str msg = 0;
492 :
493 37316 : if (MCinitClientThread(c) < 0) {
494 0 : MCcloseClient(c);
495 0 : return MAL_SUCCEED;
496 : }
497 : /*
498 : * A stack frame is initialized to keep track of global variables.
499 : * The scenarios are run until we finally close the last one.
500 : */
501 37325 : mb = c->curprg->def;
502 37325 : if (c->glb == NULL)
503 0 : c->glb = newGlobalStack(MAXGLOBALS + mb->vsize);
504 37325 : if (c->glb == NULL) {
505 0 : MCcloseClient(c);
506 0 : throw(MAL, "serveClient", SQLSTATE(HY013) MAL_MALLOC_FAIL);
507 : } else {
508 37325 : c->glb->stktop = mb->vtop;
509 37325 : c->glb->blk = mb;
510 : }
511 :
512 37325 : if (c->scenario == 0)
513 0 : msg = defaultScenario(c);
514 37325 : if (msg) {
515 0 : MCcloseClient(c);
516 0 : return msg;
517 : } else {
518 37325 : do {
519 37325 : do {
520 37325 : MT_thread_setworking("running scenario");
521 37323 : msg = runScenario(c);
522 37325 : freeException(msg);
523 37325 : if (c->mode == FINISHCLIENT)
524 : break;
525 0 : resetScenario(c);
526 0 : } while (c->scenario && !GDKexiting());
527 37325 : } while (c->scenario && c->mode != FINISHCLIENT && !GDKexiting());
528 : }
529 37325 : MT_thread_setworking("exiting");
530 : /* pre announce our exiting: cleaning up may take a while and we
531 : * don't want to get killed during that time for fear of
532 : * deadlocks */
533 37324 : MT_exiting_thread();
534 : /*
535 : * At this stage we should clean out the MAL block
536 : */
537 37325 : if (c->backup) {
538 0 : assert(0);
539 : freeSymbol(c->backup);
540 : c->backup = 0;
541 : }
542 :
543 37325 : if (c->curprg && c->curprg->def)
544 0 : resetMalBlk(c->curprg->def);
545 : /*
546 : if (c->curprg) {
547 : freeSymbol(c->curprg);
548 : c->curprg = 0;
549 : }
550 : */
551 :
552 37325 : MCcloseClient(c);
553 37325 : return MAL_SUCCEED;
554 : }
555 :
556 : /*
557 : * The stages of processing user requests are controlled by a scenario.
558 : * The routines below are the default implementation. The main issues
559 : * to deal after parsing it to clean out the Admin.main function from
560 : * any information added erroneously.
561 : *
562 : * Ideally this involves resetting the state of the client 'main'
563 : * function, i.e. the symbol table is reset and any instruction added
564 : * should be cleaned. Beware that the instruction table may have grown
565 : * in size.
566 : */
567 : str
568 313 : MALinitClient(Client c)
569 : {
570 313 : (void) c;
571 313 : return MAL_SUCCEED;
572 : }
573 :
574 : str
575 37357 : MALexitClient(Client c)
576 : {
577 37357 : if (c->glb && c->curprg->def && c->curprg->def->errors == MAL_SUCCEED)
578 37356 : garbageCollector(c, c->curprg->def, c->glb, TRUE);
579 37357 : c->mode = FINISHCLIENT;
580 37357 : if (c->backup) {
581 0 : assert(0);
582 : freeSymbol(c->backup);
583 : c->backup = NULL;
584 : }
585 : /* should be in the usermodule */
586 37357 : c->curprg = NULL;
587 37357 : if (c->usermodule) {
588 37357 : freeModule(c->usermodule);
589 37357 : c->usermodule = NULL;
590 : }
591 37357 : return NULL;
592 : }
593 :
594 : static str
595 10970 : MALreader(Client c)
596 : {
597 10970 : if (MCreadClient(c) > 0)
598 : return MAL_SUCCEED;
599 498 : MT_lock_set(&mal_contextLock);
600 498 : c->mode = FINISHCLIENT;
601 498 : MT_lock_unset(&mal_contextLock);
602 498 : if (c->fdin)
603 498 : c->fdin->buf[c->fdin->pos] = 0;
604 : return MAL_SUCCEED;
605 : }
606 :
607 : /* Before compiling a large string, it makes sense to allocate
608 : * approximately enough space to keep the intermediate
609 : * code. Otherwise, we end up with a repeated extend on the MAL block,
610 : * which really consumes a lot of memcpy resources. The average MAL
611 : * string length could been derived from the test cases. An error in
612 : * the estimate is more expensive than just counting the lines.
613 : */
614 : static int
615 10472 : prepareMalBlk(MalBlkPtr mb, const char *s)
616 : {
617 10472 : int cnt = STMT_INCREMENT;
618 :
619 10472 : if (s && *s) {
620 24505 : while ((s = strchr(s + 1, '\n')) != NULL)
621 14033 : cnt++;
622 : }
623 10472 : cnt = (int) (cnt * 1.1);
624 10472 : return resizeMalBlk(mb, cnt);
625 : }
626 :
627 : str
628 10472 : MALparser(Client c)
629 : {
630 10472 : InstrPtr p;
631 10472 : str msg = MAL_SUCCEED;
632 :
633 10472 : assert(c->curprg->def->errors == NULL);
634 10472 : c->curprg->def->errors = 0;
635 :
636 10472 : if (prepareMalBlk(c->curprg->def, CURRENT(c)) < 0)
637 0 : throw(MAL, "mal.parser", "Failed to prepare");
638 10471 : parseMAL(c, c->curprg, 0, INT_MAX, 0);
639 :
640 : /* now the parsing is done we should advance the stream */
641 10471 : c->fdin->pos += c->yycur;
642 10471 : c->yycur = 0;
643 10471 : c->qryctx.starttime = GDKusec();
644 10470 : c->qryctx.endtime = c->querytimeout ? c->qryctx.starttime + c->querytimeout : 0;
645 :
646 : /* check for unfinished blocks */
647 10470 : if (!c->curprg->def->errors && c->blkmode)
648 : return MAL_SUCCEED;
649 : /* empty files should be skipped as well */
650 10220 : if (c->curprg->def->stop == 1) {
651 243 : if ((msg = c->curprg->def->errors))
652 53 : c->curprg->def->errors = 0;
653 243 : return msg;
654 : }
655 :
656 9977 : p = getInstrPtr(c->curprg->def, 0);
657 9977 : if (p->token != FUNCTIONsymbol) {
658 0 : msg = c->curprg->def->errors;
659 0 : c->curprg->def->errors = 0;
660 0 : MSresetStack(c, c->curprg->def, c->glb);
661 0 : resetMalTypes(c->curprg->def, 1);
662 0 : return msg;
663 : }
664 9977 : pushEndInstruction(c->curprg->def);
665 9977 : msg = chkProgram(c->usermodule, c->curprg->def);
666 9975 : if (msg != MAL_SUCCEED || (msg = c->curprg->def->errors)) {
667 47 : c->curprg->def->errors = 0;
668 47 : MSresetStack(c, c->curprg->def, c->glb);
669 47 : resetMalTypes(c->curprg->def, 1);
670 47 : return msg;
671 : }
672 : return MAL_SUCCEED;
673 : }
674 :
675 : int
676 258739 : MALcommentsOnly(MalBlkPtr mb)
677 : {
678 258739 : int i;
679 :
680 258827 : for (i = 1; i < mb->stop; i++)
681 259308 : if (mb->stmt[i]->token != REMsymbol)
682 : return 0;
683 : return 1;
684 : }
685 :
686 : /*
687 : * The default MAL optimizer includes a final call to
688 : * the multiplex expander.
689 : * We should take care of functions marked as 'inline',
690 : * because they should be kept in raw form.
691 : * Their optimization takes place after inlining.
692 : */
693 : static str
694 10117 : MALoptimizer(Client c)
695 : {
696 10117 : str msg;
697 :
698 10117 : if (c->curprg->def->inlineProp)
699 : return MAL_SUCCEED;
700 : // only a signature statement can be skipped
701 10117 : if (c->curprg->def->stop == 1)
702 : return MAL_SUCCEED;
703 9927 : msg = optimizeMALBlock(c, c->curprg->def);
704 : /*
705 : if( msg == MAL_SUCCEED)
706 : msg = OPTmultiplexSimple(c, c->curprg->def);
707 : */
708 9927 : return msg;
709 : }
710 :
711 : static str
712 10720 : MALengine_(Client c)
713 : {
714 10720 : Symbol prg;
715 10720 : str msg = MAL_SUCCEED;
716 :
717 10971 : do {
718 10971 : if ((msg = MALreader(c)) != MAL_SUCCEED)
719 0 : return msg;
720 10970 : if (c->mode == FINISHCLIENT)
721 : return msg;
722 10472 : if ((msg = MALparser(c)) != MAL_SUCCEED)
723 100 : return msg;
724 10369 : } while (c->blkmode);
725 : /*
726 : if (c->blkmode)
727 : return MAL_SUCCEED;
728 : */
729 10118 : if ((msg = MALoptimizer(c)) != MAL_SUCCEED)
730 : return msg;
731 10114 : prg = c->curprg;
732 10114 : if (prg == NULL)
733 0 : throw(SYNTAX, "mal.engine", SYNTAX_SIGNATURE);
734 10114 : if (prg->def == NULL)
735 0 : throw(SYNTAX, "mal.engine", SYNTAX_SIGNATURE);
736 :
737 10114 : if (prg->def->errors != MAL_SUCCEED) {
738 0 : msg = prg->def->errors;
739 0 : prg->def->errors = NULL;
740 0 : MSresetStack(c, c->curprg->def, c->glb);
741 0 : resetMalTypes(c->curprg->def, 1);
742 0 : return msg;
743 : }
744 10114 : if (prg->def->stop == 1 || MALcommentsOnly(prg->def))
745 190 : return 0; /* empty block */
746 9924 : if (c->glb) {
747 9924 : if (prg->def && c->glb->stksize < prg->def->vsize) {
748 4 : c->glb = reallocGlobalStack(c->glb, prg->def->vsize);
749 4 : if (c->glb == NULL)
750 0 : throw(MAL, "mal.engine", SQLSTATE(HY013) MAL_MALLOC_FAIL);
751 : }
752 9924 : c->glb->stktop = prg->def->vtop;
753 9924 : c->glb->blk = prg->def;
754 : }
755 :
756 : /*
757 : * In interactive mode we should avoid early garbage collection of values.
758 : * This can be controlled by the clean up control at the instruction level
759 : * and marking all non-temporary variables as being (potentially) used.
760 : */
761 9924 : if (c->glb) {
762 9924 : c->glb->pcup = 0;
763 9924 : c->glb->keepAlive = TRUE; /* no garbage collection */
764 : }
765 9924 : if (prg->def->errors == MAL_SUCCEED)
766 9922 : msg = (str) runMAL(c, prg->def, 0, c->glb);
767 9928 : if (msg) {
768 : /* ignore "internal" exceptions */
769 26 : if (strstr(msg, "client.quit")) {
770 0 : freeException(msg);
771 0 : msg = MAL_SUCCEED;
772 : }
773 : }
774 9928 : MSresetStack(c, prg->def, c->glb);
775 9926 : resetMalTypes(prg->def, 1);
776 9926 : if (c->glb) {
777 : /* for global stacks avoid reinitialization from this point */
778 9926 : c->glb->stkbot = prg->def->vtop;
779 : }
780 :
781 9926 : if (prg->def->errors)
782 0 : freeException(prg->def->errors);
783 9926 : prg->def->errors = NULL;
784 9926 : return msg;
785 : }
786 :
787 : void
788 10720 : MALengine(Client c)
789 : {
790 10720 : str msg = MALengine_(c);
791 10720 : if (msg) {
792 : /* don't print exception decoration, just the message */
793 318 : char *n = NULL;
794 : char *o = msg;
795 318 : while ((n = strchr(o, '\n')) != NULL) {
796 188 : if (*o == '!')
797 0 : o++;
798 188 : mnstr_printf(c->fdout, "!%.*s\n", (int) (n - o), o);
799 188 : o = ++n;
800 : }
801 130 : if (*o != 0) {
802 39 : if (*o == '!')
803 4 : o++;
804 39 : mnstr_printf(c->fdout, "!%s\n", o);
805 : }
806 130 : freeException(msg);
807 : }
808 10720 : }
809 :
810 : /* Hypothetical, optimizers may massage the plan in such a way
811 : * that multiple passes are needed.
812 : * However, the current SQL driven approach only expects a single
813 : * non-repeating pipeline of optimizer steps stored at the end of the MAL block.
814 : * A single scan forward over the MAL plan is assumed.
815 : */
816 : str
817 526939 : optimizeMALBlock(Client cntxt, MalBlkPtr mb)
818 : {
819 526939 : InstrPtr p;
820 526939 : int pc, oldstop;
821 526939 : str msg = MAL_SUCCEED;
822 526939 : int cnt = 0;
823 526939 : int actions = 0;
824 526939 : lng clk = GDKusec();
825 :
826 : /* assume the type and flow have been checked already */
827 : /* SQL functions intended to be inlined should not be optimized */
828 526994 : if (mb->inlineProp)
829 : return 0;
830 :
831 526827 : mb->optimize = 0;
832 526827 : if (mb->errors)
833 0 : throw(MAL, "optimizer.MALoptimizer",
834 : SQLSTATE(42000) "Start with inconsistent MAL plan");
835 :
836 : // strong defense line, assure that MAL plan is initially correct
837 526827 : if (mb->errors == 0 && mb->stop > 1) {
838 526781 : resetMalTypes(mb, mb->stop);
839 526810 : msg = chkTypes(cntxt->usermodule, mb, FALSE);
840 525800 : if (!msg)
841 526091 : msg = chkFlow(mb);
842 526589 : if (!msg)
843 526257 : msg = chkDeclarations(mb);
844 526304 : if (msg)
845 6 : return msg;
846 526630 : if (mb->errors != MAL_SUCCEED) {
847 0 : msg = mb->errors;
848 0 : mb->errors = MAL_SUCCEED;
849 0 : return msg;
850 : }
851 : }
852 :
853 526676 : oldstop = mb->stop;
854 37527256 : for (pc = 0; pc < mb->stop; pc++) {
855 37000457 : p = getInstrPtr(mb, pc);
856 37000457 : if (getModuleId(p) == optimizerRef && p->fcn && p->token != REMsymbol) {
857 12463775 : actions++;
858 12463775 : msg = (*(str (*)(Client, MalBlkPtr, MalStkPtr, InstrPtr)) p->fcn) (cntxt, mb, 0, p);
859 12463898 : if (mb->errors) {
860 0 : freeException(msg);
861 0 : msg = mb->errors;
862 0 : mb->errors = NULL;
863 : }
864 12463898 : if (msg) {
865 0 : str place = getExceptionPlace(msg);
866 0 : str nmsg = NULL;
867 0 : if (place) {
868 0 : nmsg = createException(getExceptionType(msg), place, "%s",
869 : getExceptionMessageAndState(msg));
870 0 : GDKfree(place);
871 : }
872 0 : if (nmsg) {
873 0 : freeException(msg);
874 0 : msg = nmsg;
875 : }
876 0 : goto wrapup;
877 : }
878 12463898 : if (cntxt->mode == FINISHCLIENT) {
879 0 : mb->optimize = GDKusec() - clk;
880 0 : throw(MAL, "optimizeMALBlock",
881 : SQLSTATE(42000) "prematurely stopped client");
882 : }
883 : /* the MAL block may have changed */
884 12463898 : pc += mb->stop - oldstop - 1;
885 12463898 : oldstop = mb->stop;
886 : }
887 : }
888 :
889 526799 : wrapup:
890 : /* Keep the total time spent on optimizing the plan for inspection */
891 526799 : if (actions > 0 && msg == MAL_SUCCEED) {
892 516974 : mb->optimize = GDKusec() - clk;
893 516995 : p = newStmt(mb, optimizerRef, totalRef);
894 517006 : if (p == NULL) {
895 0 : throw(MAL, "optimizer.MALoptimizer",
896 : SQLSTATE(HY013) MAL_MALLOC_FAIL);
897 : }
898 517006 : p->token = REMsymbol;
899 517006 : p = pushInt(mb, p, actions);
900 516977 : p = pushLng(mb, p, mb->optimize);
901 516924 : pushInstruction(mb, p);
902 : }
903 526691 : if (cnt >= mb->stop)
904 0 : throw(MAL, "optimizer.MALoptimizer", SQLSTATE(42000) OPTIMIZER_CYCLE);
905 : return msg;
906 : }
|