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 329 : malBootstrap(char *modules[], bool embedded, const char *initpasswd)
37 : {
38 329 : Client c;
39 329 : str msg = MAL_SUCCEED;
40 :
41 329 : c = MCinitClient(MAL_ADMIN, NULL, NULL);
42 329 : if (c == NULL) {
43 0 : throw(MAL, "malBootstrap", "Failed to initialize client");
44 : }
45 329 : MT_thread_set_qry_ctx(NULL);
46 329 : assert(c != NULL);
47 329 : c->curmodule = c->usermodule = userModule();
48 329 : if (c->usermodule == NULL) {
49 0 : MCcloseClient(c);
50 0 : throw(MAL, "malBootstrap", "Failed to initialize client MAL module");
51 : }
52 329 : if ((msg = defaultScenario(c))) {
53 0 : MCcloseClient(c);
54 0 : return msg;
55 : }
56 329 : if ((msg = MSinitClientPrg(c, "user", "main")) != MAL_SUCCEED) {
57 0 : MCcloseClient(c);
58 0 : return msg;
59 : }
60 :
61 329 : if (MCinitClientThread(c) < 0) {
62 0 : MCcloseClient(c);
63 0 : throw(MAL, "malBootstrap", "Failed to create client thread");
64 : }
65 329 : if ((msg = malIncludeModules(c, modules, 0, embedded, initpasswd)) != MAL_SUCCEED) {
66 0 : MCcloseClient(c);
67 0 : return msg;
68 : }
69 328 : MCcloseClient(c);
70 328 : 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 305768 : MSresetClientPrg(Client cntxt, const char *mod, const char *fcn)
89 : {
90 305768 : MalBlkPtr mb;
91 305768 : InstrPtr p;
92 :
93 305768 : mb = cntxt->curprg->def;
94 305768 : mb->stop = 1;
95 305768 : mb->errors = MAL_SUCCEED;
96 305768 : p = mb->stmt[0];
97 :
98 305768 : p->gc = 0;
99 305768 : p->retc = 1;
100 305768 : p->argc = 1;
101 305768 : p->argv[0] = 0;
102 :
103 305768 : setModuleId(p, mod);
104 305768 : setFunctionId(p, fcn);
105 305768 : if (findVariable(mb, fcn) < 0)
106 305758 : if ((p->argv[0] = newVariable(mb, fcn, strlen(fcn), TYPE_void)) < 0)
107 0 : throw(MAL, "resetClientPrg", SQLSTATE(HY013) MAL_MALLOC_FAIL);
108 :
109 305768 : setVarType(mb, findVariable(mb, fcn), TYPE_void);
110 305768 : return MAL_SUCCEED;
111 : }
112 :
113 : /*
114 : * Create a new container block
115 : */
116 :
117 : str
118 355012 : MSinitClientPrg(Client cntxt, const char *mod, const char *nme)
119 : {
120 355012 : int idx;
121 :
122 355012 : if (cntxt->curprg && idcmp(nme, cntxt->curprg->name) == 0)
123 305768 : return MSresetClientPrg(cntxt, putName(mod), putName(nme));
124 49244 : cntxt->curprg = newFunction(putName(mod), putName(nme), FUNCTIONsymbol);
125 49243 : if (cntxt->curprg == 0)
126 0 : throw(MAL, "initClientPrg", SQLSTATE(HY013) MAL_MALLOC_FAIL);
127 49243 : if ((idx = findVariable(cntxt->curprg->def, "main")) >= 0)
128 38802 : setVarType(cntxt->curprg->def, idx, TYPE_void);
129 49243 : insertSymbol(cntxt->usermodule, cntxt->curprg);
130 :
131 49243 : if (cntxt->glb == NULL)
132 38824 : cntxt->glb = newGlobalStack(MAXGLOBALS + cntxt->curprg->def->vsize);
133 49243 : if (cntxt->glb == NULL)
134 0 : throw(MAL, "initClientPrg", SQLSTATE(HY013) MAL_MALLOC_FAIL);
135 49243 : assert(cntxt->curprg->def != NULL);
136 49243 : 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 165 : exit_streams(bstream *fin, stream *fout)
150 : {
151 165 : if (fout && fout != GDKstdout) {
152 165 : mnstr_flush(fout, MNSTR_FLUSH_DATA);
153 165 : close_stream(fout);
154 : }
155 165 : if (fin)
156 165 : bstream_destroy(fin);
157 165 : }
158 :
159 : static const char mal_enableflag[] = "mal_for_all";
160 :
161 : static bool
162 4681 : is_exiting(void *data)
163 : {
164 4681 : (void) data;
165 4681 : 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 38641 : MSscheduleClient(str command, str peer, str challenge, bstream *fin, stream *fout,
190 : protocol_version protocol, size_t blocksize)
191 : {
192 38641 : char *user = command, *algo = NULL, *passwd = NULL, *lang = NULL,
193 38641 : *handshake_opts = NULL;
194 38641 : char *database = NULL, *s;
195 38641 : const char *dbname;
196 38641 : str msg = MAL_SUCCEED;
197 38641 : bool filetrans = false;
198 38641 : Client c;
199 :
200 38641 : MT_thread_set_qry_ctx(NULL);
201 :
202 : /* decode BIG/LIT:user:{cypher}passwordchal:lang:database: line */
203 :
204 : /* byte order */
205 38641 : s = strchr(user, ':');
206 38641 : if (s) {
207 38476 : *s = 0;
208 38476 : mnstr_set_bigendian(fin->s, strcmp(user, "BIG") == 0);
209 38476 : user = s + 1;
210 : } else {
211 165 : mnstr_printf(fout, "!incomplete challenge '%s'\n", user);
212 165 : exit_streams(fin, fout);
213 165 : GDKfree(command);
214 342 : return;
215 : }
216 :
217 : /* passwd */
218 38476 : s = strchr(user, ':');
219 38476 : if (s) {
220 38476 : *s = 0;
221 38476 : passwd = s + 1;
222 : /* decode algorithm, i.e. {plain}mypasswordchallenge */
223 38476 : 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 38476 : algo = passwd + 1;
230 38476 : s = strchr(algo, '}');
231 38476 : 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 38476 : *s = 0;
238 38476 : 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 38476 : s = strchr(passwd, ':');
248 38476 : if (s) {
249 38476 : *s = 0;
250 38476 : 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 38476 : s = strchr(lang, ':');
260 38476 : if (s) {
261 38476 : *s = 0;
262 38476 : database = s + 1;
263 : /* we can have stuff following, make it void */
264 38476 : s = strchr(database, ':');
265 38476 : if (s)
266 38476 : *s++ = 0;
267 : }
268 :
269 38476 : if (s && strncmp(s, "FILETRANS:", 10) == 0) {
270 38169 : s += 10;
271 38169 : filetrans = true;
272 307 : } else if (s && s[0] == ':') {
273 0 : s += 1;
274 0 : filetrans = false;
275 : }
276 :
277 38476 : if (s && strchr(s, ':') != NULL) {
278 38169 : handshake_opts = s;
279 38169 : s = strchr(s, ':');
280 38169 : *s++ = '\0';
281 : }
282 38476 : dbname = GDKgetenv("gdk_dbname");
283 38476 : if (database != NULL && database[0] != '\0' &&
284 37461 : 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 38476 : c = MCinitClient(0, fin, fout);
295 38476 : 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 38476 : c->filetrans = filetrans;
307 38476 : c->handshake_options = handshake_opts ? strdup(handshake_opts) : NULL;
308 : /* move this back !! */
309 38476 : if (c->usermodule == 0) {
310 38476 : c->curmodule = c->usermodule = userModule();
311 38476 : 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 38476 : 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 38476 : if (!GDKgetenv_isyes(mal_enableflag)
325 38476 : && 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 38473 : 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 38473 : c->username = GDKstrdup(user);
344 38473 : if (peer)
345 38473 : 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 38472 : c->protocol = protocol;
362 38472 : c->blocksize = blocksize;
363 :
364 38472 : if (c->initClient) {
365 38472 : 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 38464 : GDKfree(command);
375 :
376 38462 : mnstr_settimeout(c->fdin->s, 50, is_exiting, NULL);
377 38464 : msg = MSserveClient(c);
378 38464 : if (msg != MAL_SUCCEED) {
379 0 : mnstr_printf(fout, "!could not serve client\n");
380 0 : exit_streams(fin, fout);
381 0 : freeException(msg);
382 : }
383 : }
384 :
385 : /*
386 : * After the client initialization has been finished, we can start the
387 : * interaction protocol. This involves parsing the input in the context
388 : * of an already defined procedure and upon success, its execution.
389 : *
390 : * In essence, this calls for an incremental parsing operation, because
391 : * we should wait until a complete basic block has been detected. Test,
392 : * first collect the instructions before we take them all.
393 : *
394 : * In interactive mode, we should remove the instructions before
395 : * accepting new ones. The function signature remains the same and the
396 : * symbol table should also not be affected. Aside from removing
397 : * instruction, we should also condense the variable stack, i.e.
398 : * removing at least the temporary variables, but maybe everything
399 : * beyond a previous defined point.
400 : *
401 : * Beware that we have to cleanup the global stack as well. This to
402 : * avoid subsequent calls to find garbage information. However, this
403 : * action is only required after a successful execution. Otherwise,
404 : * garbage collection is not needed.
405 : */
406 : void
407 312094 : MSresetInstructions(MalBlkPtr mb, int start)
408 : {
409 312094 : int i;
410 312094 : InstrPtr p;
411 :
412 76383075 : for (i = start; i < mb->ssize; i++) {
413 76070981 : p = getInstrPtr(mb, i);
414 76070981 : if (p)
415 38775 : freeInstruction(p);
416 76070981 : mb->stmt[i] = NULL;
417 : }
418 312094 : mb->stop = start;
419 312094 : }
420 :
421 : /*
422 : * MAL instructions generate variables.
423 : * The values of temporary variables should be cleaned at the end of a call
424 : * The values of global variables are retained.
425 : * Global variables should not start with C_ or X_
426 : */
427 : void
428 9979 : MSresetStack(Client cntxt, MalBlkPtr mb, MalStkPtr glb)
429 : {
430 9979 : InstrPtr sig = getInstrPtr(mb, 0);
431 9979 : int i, k = sig->argc;
432 :
433 9979 : if (mb->errors == MAL_SUCCEED) {
434 1251873 : for (i = sig->argc; i < mb->vtop; i++) {
435 1241894 : if (glb && i < glb->stktop && isTmpVar(mb, i) && !glb->keepTmps) {
436 7516 : if (mb->var[i].name)
437 2 : GDKfree(mb->var[i].name);
438 : /* clean stack entry */
439 7516 : garbageElement(cntxt, &glb->stk[i]);
440 7516 : glb->stk[i].vtype = TYPE_int;
441 7516 : glb->stk[i].len = 0;
442 7516 : glb->stk[i].val.pval = 0;
443 7516 : if (isVarConstant(mb, i))
444 4843 : garbageElement(cntxt, &mb->var[i].value);
445 : } else {
446 : /* compress the global variable list and stack */
447 1234378 : mb->var[k] = mb->var[i];
448 1234378 : glb->stk[k] = glb->stk[i];
449 1234378 : setVarUsed(mb, k);
450 1234378 : setVarInit(mb, k);
451 1234378 : if (i != k) {
452 68 : glb->stk[i].vtype = TYPE_int;
453 68 : glb->stk[i].len = 0;
454 68 : glb->stk[i].val.pval = 0;
455 68 : clrVarConstant(mb, i);
456 68 : clrVarCleanup(mb, i);
457 : }
458 1234378 : k++;
459 : }
460 : }
461 : }
462 9979 : assert(k <= mb->vsize);
463 9979 : mb->vtop = k;
464 9979 : }
465 :
466 : /* The symbol table be become filled with constant values to be garbage collected
467 : * The signature is always left behind.
468 : */
469 :
470 : void
471 0 : MSresetVariables(MalBlkPtr mb)
472 : {
473 0 : InstrPtr sig = getInstrPtr(mb, 0);
474 0 : int i;
475 :
476 0 : if (mb->errors == MAL_SUCCEED)
477 0 : for (i = sig->argc; i < mb->vtop; i++)
478 0 : if (isVarConstant(mb, i)) {
479 0 : VALclear(&getVarConstant(mb, i));
480 0 : clrVarConstant(mb, i);
481 : }
482 0 : }
483 :
484 : /*
485 : * Here we start the client. We need to initialize and allocate space
486 : * for the global variables. Thereafter it is up to the scenario
487 : * interpreter to process input.
488 : */
489 : static str
490 38464 : MSserveClient(Client c)
491 : {
492 38464 : MalBlkPtr mb;
493 38464 : str msg = 0;
494 :
495 38464 : if (MCinitClientThread(c) < 0) {
496 0 : MCcloseClient(c);
497 0 : return MAL_SUCCEED;
498 : }
499 : /*
500 : * A stack frame is initialized to keep track of global variables.
501 : * The scenarios are run until we finally close the last one.
502 : */
503 38464 : mb = c->curprg->def;
504 38464 : if (c->glb == NULL)
505 0 : c->glb = newGlobalStack(MAXGLOBALS + mb->vsize);
506 38464 : if (c->glb == NULL) {
507 0 : MCcloseClient(c);
508 0 : throw(MAL, "serveClient", SQLSTATE(HY013) MAL_MALLOC_FAIL);
509 : } else {
510 38464 : c->glb->stktop = mb->vtop;
511 38464 : c->glb->blk = mb;
512 : }
513 :
514 38464 : if (c->scenario == 0)
515 0 : msg = defaultScenario(c);
516 38464 : if (msg) {
517 0 : MCcloseClient(c);
518 0 : return msg;
519 : } else {
520 38464 : do {
521 38464 : do {
522 38464 : MT_thread_setworking("running scenario");
523 38464 : msg = runScenario(c);
524 38464 : freeException(msg);
525 38464 : if (c->mode == FINISHCLIENT)
526 : break;
527 0 : resetScenario(c);
528 0 : } while (c->scenario && !GDKexiting());
529 38464 : } while (c->scenario && c->mode != FINISHCLIENT && !GDKexiting());
530 : }
531 38464 : MT_thread_setworking("exiting");
532 : /* pre announce our exiting: cleaning up may take a while and we
533 : * don't want to get killed during that time for fear of
534 : * deadlocks */
535 38464 : MT_exiting_thread();
536 : /*
537 : * At this stage we should clean out the MAL block
538 : */
539 38464 : if (c->backup) {
540 0 : assert(0);
541 : freeSymbol(c->backup);
542 : c->backup = 0;
543 : }
544 :
545 38464 : if (c->curprg && c->curprg->def)
546 0 : resetMalBlk(c->curprg->def);
547 : /*
548 : if (c->curprg) {
549 : freeSymbol(c->curprg);
550 : c->curprg = 0;
551 : }
552 : */
553 :
554 38464 : MCcloseClient(c);
555 38464 : return MAL_SUCCEED;
556 : }
557 :
558 : /*
559 : * The stages of processing user requests are controlled by a scenario.
560 : * The routines below are the default implementation. The main issues
561 : * to deal after parsing it to clean out the Admin.main function from
562 : * any information added erroneously.
563 : *
564 : * Ideally this involves resetting the state of the client 'main'
565 : * function, i.e. the symbol table is reset and any instruction added
566 : * should be cleaned. Beware that the instruction table may have grown
567 : * in size.
568 : */
569 : str
570 313 : MALinitClient(Client c)
571 : {
572 313 : (void) c;
573 313 : return MAL_SUCCEED;
574 : }
575 :
576 : str
577 38496 : MALexitClient(Client c)
578 : {
579 38496 : if (c->glb && c->curprg->def && c->curprg->def->errors == MAL_SUCCEED)
580 38495 : garbageCollector(c, c->curprg->def, c->glb, TRUE);
581 38496 : c->mode = FINISHCLIENT;
582 38496 : if (c->backup) {
583 0 : assert(0);
584 : freeSymbol(c->backup);
585 : c->backup = NULL;
586 : }
587 : /* should be in the usermodule */
588 38496 : c->curprg = NULL;
589 38496 : if (c->usermodule) {
590 38496 : freeModule(c->usermodule);
591 38496 : c->usermodule = NULL;
592 : }
593 38496 : return NULL;
594 : }
595 :
596 : static str
597 10970 : MALreader(Client c)
598 : {
599 10970 : if (MCreadClient(c) > 0)
600 : return MAL_SUCCEED;
601 498 : MT_lock_set(&mal_contextLock);
602 498 : c->mode = FINISHCLIENT;
603 498 : MT_lock_unset(&mal_contextLock);
604 498 : if (c->fdin)
605 498 : c->fdin->buf[c->fdin->pos] = 0;
606 : return MAL_SUCCEED;
607 : }
608 :
609 : /* Before compiling a large string, it makes sense to allocate
610 : * approximately enough space to keep the intermediate
611 : * code. Otherwise, we end up with a repeated extend on the MAL block,
612 : * which really consumes a lot of memcpy resources. The average MAL
613 : * string length could been derived from the test cases. An error in
614 : * the estimate is more expensive than just counting the lines.
615 : */
616 : static int
617 10472 : prepareMalBlk(MalBlkPtr mb, const char *s)
618 : {
619 10472 : int cnt = STMT_INCREMENT;
620 :
621 10472 : if (s && *s) {
622 24505 : while ((s = strchr(s + 1, '\n')) != NULL)
623 14033 : cnt++;
624 : }
625 10472 : cnt = (int) (cnt * 1.1);
626 10472 : return resizeMalBlk(mb, cnt);
627 : }
628 :
629 : str
630 10472 : MALparser(Client c)
631 : {
632 10472 : InstrPtr p;
633 10472 : str msg = MAL_SUCCEED;
634 :
635 10472 : assert(c->curprg->def->errors == NULL);
636 10472 : c->curprg->def->errors = 0;
637 :
638 10472 : if (prepareMalBlk(c->curprg->def, CURRENT(c)) < 0)
639 0 : throw(MAL, "mal.parser", "Failed to prepare");
640 10472 : parseMAL(c, c->curprg, 0, INT_MAX, 0);
641 :
642 : /* now the parsing is done we should advance the stream */
643 10472 : c->fdin->pos += c->yycur;
644 10472 : c->yycur = 0;
645 10472 : c->qryctx.starttime = GDKusec();
646 10472 : c->qryctx.endtime = c->querytimeout ? c->qryctx.starttime + c->querytimeout : 0;
647 :
648 : /* check for unfinished blocks */
649 10472 : if (!c->curprg->def->errors && c->blkmode)
650 : return MAL_SUCCEED;
651 : /* empty files should be skipped as well */
652 10222 : if (c->curprg->def->stop == 1) {
653 243 : if ((msg = c->curprg->def->errors))
654 53 : c->curprg->def->errors = 0;
655 243 : return msg;
656 : }
657 :
658 9979 : p = getInstrPtr(c->curprg->def, 0);
659 9979 : if (p->token != FUNCTIONsymbol) {
660 0 : msg = c->curprg->def->errors;
661 0 : c->curprg->def->errors = 0;
662 0 : MSresetStack(c, c->curprg->def, c->glb);
663 0 : resetMalTypes(c->curprg->def, 1);
664 0 : return msg;
665 : }
666 9979 : pushEndInstruction(c->curprg->def);
667 9979 : msg = chkProgram(c->usermodule, c->curprg->def);
668 9979 : if (msg != MAL_SUCCEED || (msg = c->curprg->def->errors)) {
669 47 : c->curprg->def->errors = 0;
670 47 : MSresetStack(c, c->curprg->def, c->glb);
671 47 : resetMalTypes(c->curprg->def, 1);
672 47 : return msg;
673 : }
674 : return MAL_SUCCEED;
675 : }
676 :
677 : int
678 265041 : MALcommentsOnly(MalBlkPtr mb)
679 : {
680 265041 : int i;
681 :
682 265129 : for (i = 1; i < mb->stop; i++)
683 265129 : if (mb->stmt[i]->token != REMsymbol)
684 : return 0;
685 : return 1;
686 : }
687 :
688 : /*
689 : * The default MAL optimizer includes a final call to
690 : * the multiplex expander.
691 : * We should take care of functions marked as 'inline',
692 : * because they should be kept in raw form.
693 : * Their optimization takes place after inlining.
694 : */
695 : static str
696 10122 : MALoptimizer(Client c)
697 : {
698 10122 : str msg;
699 :
700 10122 : if (c->curprg->def->inlineProp)
701 : return MAL_SUCCEED;
702 : // only a signature statement can be skipped
703 10122 : if (c->curprg->def->stop == 1)
704 : return MAL_SUCCEED;
705 9932 : msg = optimizeMALBlock(c, c->curprg->def);
706 : /*
707 : if( msg == MAL_SUCCEED)
708 : msg = OPTmultiplexSimple(c, c->curprg->def);
709 : */
710 9932 : return msg;
711 : }
712 :
713 : static str
714 10720 : MALengine_(Client c)
715 : {
716 10720 : Symbol prg;
717 10720 : str msg = MAL_SUCCEED;
718 :
719 10970 : do {
720 10970 : if ((msg = MALreader(c)) != MAL_SUCCEED)
721 0 : return msg;
722 10969 : if (c->mode == FINISHCLIENT)
723 : return msg;
724 10471 : if ((msg = MALparser(c)) != MAL_SUCCEED)
725 100 : return msg;
726 10372 : } while (c->blkmode);
727 : /*
728 : if (c->blkmode)
729 : return MAL_SUCCEED;
730 : */
731 10122 : if ((msg = MALoptimizer(c)) != MAL_SUCCEED)
732 : return msg;
733 10122 : prg = c->curprg;
734 10122 : if (prg == NULL)
735 0 : throw(SYNTAX, "mal.engine", SYNTAX_SIGNATURE);
736 10122 : if (prg->def == NULL)
737 0 : throw(SYNTAX, "mal.engine", SYNTAX_SIGNATURE);
738 :
739 10122 : if (prg->def->errors != MAL_SUCCEED) {
740 0 : msg = prg->def->errors;
741 0 : prg->def->errors = NULL;
742 0 : MSresetStack(c, c->curprg->def, c->glb);
743 0 : resetMalTypes(c->curprg->def, 1);
744 0 : return msg;
745 : }
746 10122 : if (prg->def->stop == 1 || MALcommentsOnly(prg->def))
747 190 : return 0; /* empty block */
748 9932 : if (c->glb) {
749 9932 : if (prg->def && c->glb->stksize < prg->def->vsize) {
750 4 : c->glb = reallocGlobalStack(c->glb, prg->def->vsize);
751 4 : if (c->glb == NULL)
752 0 : throw(MAL, "mal.engine", SQLSTATE(HY013) MAL_MALLOC_FAIL);
753 : }
754 9932 : c->glb->stktop = prg->def->vtop;
755 9932 : c->glb->blk = prg->def;
756 : }
757 :
758 : /*
759 : * In interactive mode we should avoid early garbage collection of values.
760 : * This can be controlled by the clean up control at the instruction level
761 : * and marking all non-temporary variables as being (potentially) used.
762 : */
763 9932 : if (c->glb) {
764 9932 : c->glb->pcup = 0;
765 9932 : c->glb->keepAlive = TRUE; /* no garbage collection */
766 : }
767 9932 : if (prg->def->errors == MAL_SUCCEED)
768 9932 : msg = (str) runMAL(c, prg->def, 0, c->glb);
769 9932 : if (msg) {
770 : /* ignore "internal" exceptions */
771 30 : if (strstr(msg, "client.quit")) {
772 0 : freeException(msg);
773 0 : msg = MAL_SUCCEED;
774 : }
775 : }
776 9932 : MSresetStack(c, prg->def, c->glb);
777 9932 : resetMalTypes(prg->def, 1);
778 9932 : if (c->glb) {
779 : /* for global stacks avoid reinitialization from this point */
780 9932 : c->glb->stkbot = prg->def->vtop;
781 : }
782 :
783 9932 : if (prg->def->errors)
784 0 : freeException(prg->def->errors);
785 9932 : prg->def->errors = NULL;
786 9932 : return msg;
787 : }
788 :
789 : void
790 10720 : MALengine(Client c)
791 : {
792 10720 : str msg = MALengine_(c);
793 10720 : if (msg) {
794 : /* don't print exception decoration, just the message */
795 324 : char *n = NULL;
796 : char *o = msg;
797 324 : while ((n = strchr(o, '\n')) != NULL) {
798 194 : if (*o == '!')
799 0 : o++;
800 194 : mnstr_printf(c->fdout, "!%.*s\n", (int) (n - o), o);
801 194 : o = ++n;
802 : }
803 130 : if (*o != 0) {
804 33 : if (*o == '!')
805 4 : o++;
806 33 : mnstr_printf(c->fdout, "!%s\n", o);
807 : }
808 130 : freeException(msg);
809 : }
810 10720 : }
811 :
812 : /* Hypothetical, optimizers may massage the plan in such a way
813 : * that multiple passes are needed.
814 : * However, the current SQL driven approach only expects a single
815 : * non-repeating pipeline of optimizer steps stored at the end of the MAL block.
816 : * A single scan forward over the MAL plan is assumed.
817 : */
818 : str
819 576344 : optimizeMALBlock(Client cntxt, MalBlkPtr mb)
820 : {
821 576344 : InstrPtr p;
822 576344 : int pc, oldstop;
823 576344 : str msg = MAL_SUCCEED;
824 576344 : int cnt = 0;
825 576344 : int actions = 0;
826 576344 : lng clk = GDKusec();
827 :
828 : /* assume the type and flow have been checked already */
829 : /* SQL functions intended to be inlined should not be optimized */
830 576344 : if (mb->inlineProp)
831 : return 0;
832 :
833 576177 : mb->optimize = 0;
834 576177 : if (mb->errors)
835 0 : throw(MAL, "optimizer.MALoptimizer",
836 : SQLSTATE(42000) "Start with inconsistent MAL plan");
837 :
838 : // strong defense line, assure that MAL plan is initially correct
839 576177 : if (mb->errors == 0 && mb->stop > 1) {
840 576177 : resetMalTypes(mb, mb->stop);
841 576175 : msg = chkTypes(cntxt->usermodule, mb, FALSE);
842 576171 : if (!msg)
843 576171 : msg = chkFlow(mb);
844 576176 : if (!msg)
845 576176 : msg = chkDeclarations(mb);
846 576169 : if (msg)
847 0 : return msg;
848 576169 : if (mb->errors != MAL_SUCCEED) {
849 0 : msg = mb->errors;
850 0 : mb->errors = MAL_SUCCEED;
851 0 : return msg;
852 : }
853 : }
854 :
855 576169 : oldstop = mb->stop;
856 42184322 : for (pc = 0; pc < mb->stop; pc++) {
857 41608146 : p = getInstrPtr(mb, pc);
858 41608146 : if (getModuleId(p) == optimizerRef && p->fcn && p->token != REMsymbol) {
859 13900829 : actions++;
860 13900829 : msg = (*(str (*)(Client, MalBlkPtr, MalStkPtr, InstrPtr)) p->fcn) (cntxt, mb, 0, p);
861 13900836 : if (mb->errors) {
862 0 : freeException(msg);
863 0 : msg = mb->errors;
864 0 : mb->errors = NULL;
865 : }
866 13900836 : if (msg) {
867 0 : str place = getExceptionPlace(msg);
868 0 : str nmsg = NULL;
869 0 : if (place) {
870 0 : nmsg = createException(getExceptionType(msg), place, "%s",
871 : getExceptionMessageAndState(msg));
872 0 : GDKfree(place);
873 : }
874 0 : if (nmsg) {
875 0 : freeException(msg);
876 0 : msg = nmsg;
877 : }
878 0 : goto wrapup;
879 : }
880 13900836 : if (cntxt->mode == FINISHCLIENT) {
881 0 : mb->optimize = GDKusec() - clk;
882 0 : throw(MAL, "optimizeMALBlock",
883 : SQLSTATE(42000) "prematurely stopped client");
884 : }
885 : /* the MAL block may have changed */
886 13900836 : pc += mb->stop - oldstop - 1;
887 13900836 : oldstop = mb->stop;
888 : }
889 : }
890 :
891 576176 : wrapup:
892 : /* Keep the total time spent on optimizing the plan for inspection */
893 576176 : if (actions > 0 && msg == MAL_SUCCEED) {
894 566332 : mb->optimize = GDKusec() - clk;
895 566332 : p = newStmt(mb, optimizerRef, totalRef);
896 566331 : if (p == NULL) {
897 0 : throw(MAL, "optimizer.MALoptimizer",
898 : SQLSTATE(HY013) MAL_MALLOC_FAIL);
899 : }
900 566331 : p->token = REMsymbol;
901 566331 : p = pushInt(mb, p, actions);
902 566329 : p = pushLng(mb, p, mb->optimize);
903 566329 : pushInstruction(mb, p);
904 : }
905 576173 : if (cnt >= mb->stop)
906 0 : throw(MAL, "optimizer.MALoptimizer", SQLSTATE(42000) OPTIMIZER_CYCLE);
907 : return msg;
908 : }
|