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 326 : malBootstrap(char *modules[], bool embedded, const char *initpasswd)
37 : {
38 326 : Client c;
39 326 : str msg = MAL_SUCCEED;
40 :
41 326 : c = MCinitClient(MAL_ADMIN, NULL, NULL);
42 326 : if (c == NULL) {
43 0 : throw(MAL, "malBootstrap", "Failed to initialize client");
44 : }
45 326 : MT_thread_set_qry_ctx(NULL);
46 326 : assert(c != NULL);
47 326 : c->curmodule = c->usermodule = userModule();
48 326 : if (c->usermodule == NULL) {
49 0 : MCcloseClient(c);
50 0 : throw(MAL, "malBootstrap", "Failed to initialize client MAL module");
51 : }
52 326 : if ((msg = defaultScenario(c))) {
53 0 : MCcloseClient(c);
54 0 : return msg;
55 : }
56 326 : if ((msg = MSinitClientPrg(c, "user", "main")) != MAL_SUCCEED) {
57 0 : MCcloseClient(c);
58 0 : return msg;
59 : }
60 :
61 326 : if (MCinitClientThread(c) < 0) {
62 0 : MCcloseClient(c);
63 0 : throw(MAL, "malBootstrap", "Failed to create client thread");
64 : }
65 326 : if ((msg = malIncludeModules(c, modules, 0, embedded, initpasswd)) != MAL_SUCCEED) {
66 0 : MCcloseClient(c);
67 0 : return msg;
68 : }
69 325 : MCcloseClient(c);
70 325 : 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 293855 : MSresetClientPrg(Client cntxt, const char *mod, const char *fcn)
89 : {
90 293855 : MalBlkPtr mb;
91 293855 : InstrPtr p;
92 :
93 293855 : mb = cntxt->curprg->def;
94 293855 : mb->stop = 1;
95 293855 : mb->errors = MAL_SUCCEED;
96 293855 : p = mb->stmt[0];
97 :
98 293855 : p->gc = 0;
99 293855 : p->retc = 1;
100 293855 : p->argc = 1;
101 293855 : p->argv[0] = 0;
102 :
103 293855 : setModuleId(p, mod);
104 293855 : setFunctionId(p, fcn);
105 293855 : if (findVariable(mb, fcn) < 0)
106 293845 : if ((p->argv[0] = newVariable(mb, fcn, strlen(fcn), TYPE_void)) < 0)
107 0 : throw(MAL, "resetClientPrg", SQLSTATE(HY013) MAL_MALLOC_FAIL);
108 :
109 293855 : setVarType(mb, findVariable(mb, fcn), TYPE_void);
110 293855 : return MAL_SUCCEED;
111 : }
112 :
113 : /*
114 : * Create a new container block
115 : */
116 :
117 : str
118 343022 : MSinitClientPrg(Client cntxt, const char *mod, const char *nme)
119 : {
120 343022 : int idx;
121 :
122 343022 : if (cntxt->curprg && idcmp(nme, cntxt->curprg->name) == 0)
123 293855 : return MSresetClientPrg(cntxt, putName(mod), putName(nme));
124 49167 : cntxt->curprg = newFunction(putName(mod), putName(nme), FUNCTIONsymbol);
125 49167 : if (cntxt->curprg == 0)
126 0 : throw(MAL, "initClientPrg", SQLSTATE(HY013) MAL_MALLOC_FAIL);
127 49167 : if ((idx = findVariable(cntxt->curprg->def, "main")) >= 0)
128 38772 : setVarType(cntxt->curprg->def, idx, TYPE_void);
129 49167 : insertSymbol(cntxt->usermodule, cntxt->curprg);
130 :
131 49166 : if (cntxt->glb == NULL)
132 38794 : cntxt->glb = newGlobalStack(MAXGLOBALS + cntxt->curprg->def->vsize);
133 49163 : if (cntxt->glb == NULL)
134 0 : throw(MAL, "initClientPrg", SQLSTATE(HY013) MAL_MALLOC_FAIL);
135 49163 : assert(cntxt->curprg->def != NULL);
136 49163 : 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 162 : exit_streams(bstream *fin, stream *fout)
150 : {
151 162 : if (fout && fout != GDKstdout) {
152 162 : mnstr_flush(fout, MNSTR_FLUSH_DATA);
153 162 : close_stream(fout);
154 : }
155 162 : if (fin)
156 162 : bstream_destroy(fin);
157 162 : }
158 :
159 : static const char mal_enableflag[] = "mal_for_all";
160 :
161 : static bool
162 4544 : is_exiting(void *data)
163 : {
164 4544 : (void) data;
165 4544 : 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 38610 : MSscheduleClient(str command, str peer, str challenge, bstream *fin, stream *fout,
190 : protocol_version protocol, size_t blocksize)
191 : {
192 38610 : char *user = command, *algo = NULL, *passwd = NULL, *lang = NULL,
193 38610 : *handshake_opts = NULL;
194 38610 : char *database = NULL, *s;
195 38610 : const char *dbname;
196 38610 : str msg = MAL_SUCCEED;
197 38610 : bool filetrans = false;
198 38610 : Client c;
199 :
200 38610 : MT_thread_set_qry_ctx(NULL);
201 :
202 : /* decode BIG/LIT:user:{cypher}passwordchal:lang:database: line */
203 :
204 : /* byte order */
205 38610 : s = strchr(user, ':');
206 38610 : if (s) {
207 38448 : *s = 0;
208 38448 : mnstr_set_bigendian(fin->s, strcmp(user, "BIG") == 0);
209 38447 : user = s + 1;
210 : } else {
211 162 : mnstr_printf(fout, "!incomplete challenge '%s'\n", user);
212 162 : exit_streams(fin, fout);
213 162 : GDKfree(command);
214 336 : return;
215 : }
216 :
217 : /* passwd */
218 38447 : s = strchr(user, ':');
219 38447 : if (s) {
220 38447 : *s = 0;
221 38447 : passwd = s + 1;
222 : /* decode algorithm, i.e. {plain}mypasswordchallenge */
223 38447 : 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 38447 : algo = passwd + 1;
230 38447 : s = strchr(algo, '}');
231 38447 : 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 38447 : *s = 0;
238 38447 : 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 38447 : s = strchr(passwd, ':');
248 38447 : if (s) {
249 38447 : *s = 0;
250 38447 : 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 38447 : s = strchr(lang, ':');
260 38447 : if (s) {
261 38447 : *s = 0;
262 38447 : database = s + 1;
263 : /* we can have stuff following, make it void */
264 38447 : s = strchr(database, ':');
265 38447 : if (s)
266 38447 : *s++ = 0;
267 : }
268 :
269 38447 : if (s && strncmp(s, "FILETRANS:", 10) == 0) {
270 38139 : s += 10;
271 38139 : filetrans = true;
272 308 : } else if (s && s[0] == ':') {
273 0 : s += 1;
274 0 : filetrans = false;
275 : }
276 :
277 38447 : if (s && strchr(s, ':') != NULL) {
278 38139 : handshake_opts = s;
279 38139 : s = strchr(s, ':');
280 38139 : *s++ = '\0';
281 : }
282 38447 : dbname = GDKgetenv("gdk_dbname");
283 38448 : if (database != NULL && database[0] != '\0' &&
284 37433 : 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 38448 : c = MCinitClient(0, fin, fout);
295 38448 : 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 38448 : c->filetrans = filetrans;
307 38448 : c->handshake_options = handshake_opts ? strdup(handshake_opts) : NULL;
308 : /* move this back !! */
309 38448 : if (c->usermodule == 0) {
310 38448 : c->curmodule = c->usermodule = userModule();
311 38448 : 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 38448 : 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 38448 : if (!GDKgetenv_isyes(mal_enableflag)
325 38448 : && strncasecmp("sql", lang, 3) != 0
326 503 : && 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 38445 : 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 38443 : c->username = GDKstrdup(user);
344 38444 : if (peer)
345 38445 : 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 38444 : c->protocol = protocol;
362 38444 : c->blocksize = blocksize;
363 :
364 38444 : if (c->initClient) {
365 38444 : 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 38436 : GDKfree(command);
375 :
376 38434 : mnstr_settimeout(c->fdin->s, 50, is_exiting, NULL);
377 38436 : msg = MSserveClient(c);
378 38436 : 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 311838 : MSresetInstructions(MalBlkPtr mb, int start)
408 : {
409 311838 : int i;
410 311838 : InstrPtr p;
411 :
412 76323120 : for (i = start; i < mb->ssize; i++) {
413 76011282 : p = getInstrPtr(mb, i);
414 76011282 : if (p)
415 38775 : freeInstruction(p);
416 76011282 : mb->stmt[i] = NULL;
417 : }
418 311838 : mb->stop = start;
419 311838 : }
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 9998 : MSresetStack(Client cntxt, MalBlkPtr mb, MalStkPtr glb)
429 : {
430 9998 : InstrPtr sig = getInstrPtr(mb, 0);
431 9998 : int i, k = sig->argc;
432 :
433 9998 : if (mb->errors == MAL_SUCCEED) {
434 1252066 : for (i = sig->argc; i < mb->vtop; i++) {
435 1242068 : if (glb && i < glb->stktop && isTmpVar(mb, i) && !glb->keepTmps) {
436 7534 : if (mb->var[i].name)
437 2 : GDKfree(mb->var[i].name);
438 : /* clean stack entry */
439 7534 : garbageElement(cntxt, &glb->stk[i]);
440 7534 : glb->stk[i].vtype = TYPE_int;
441 7534 : glb->stk[i].len = 0;
442 7534 : glb->stk[i].val.pval = 0;
443 7534 : if (isVarConstant(mb, i))
444 4854 : garbageElement(cntxt, &mb->var[i].value);
445 : } else {
446 : /* compress the global variable list and stack */
447 1234534 : mb->var[k] = mb->var[i];
448 1234534 : glb->stk[k] = glb->stk[i];
449 1234534 : setVarUsed(mb, k);
450 1234534 : setVarInit(mb, k);
451 1234534 : if (i != k) {
452 70 : glb->stk[i].vtype = TYPE_int;
453 70 : glb->stk[i].len = 0;
454 70 : glb->stk[i].val.pval = 0;
455 70 : clrVarConstant(mb, i);
456 70 : clrVarCleanup(mb, i);
457 : }
458 1234534 : k++;
459 : }
460 : }
461 : }
462 9998 : assert(k <= mb->vsize);
463 9998 : mb->vtop = k;
464 9998 : }
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 38436 : MSserveClient(Client c)
491 : {
492 38436 : MalBlkPtr mb;
493 38436 : str msg = 0;
494 :
495 38436 : 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 38436 : mb = c->curprg->def;
504 38436 : if (c->glb == NULL)
505 0 : c->glb = newGlobalStack(MAXGLOBALS + mb->vsize);
506 38436 : if (c->glb == NULL) {
507 0 : MCcloseClient(c);
508 0 : throw(MAL, "serveClient", SQLSTATE(HY013) MAL_MALLOC_FAIL);
509 : } else {
510 38436 : c->glb->stktop = mb->vtop;
511 38436 : c->glb->blk = mb;
512 : }
513 :
514 38436 : if (c->scenario == 0)
515 0 : msg = defaultScenario(c);
516 38436 : if (msg) {
517 0 : MCcloseClient(c);
518 0 : return msg;
519 : } else {
520 38436 : do {
521 38436 : do {
522 38436 : MT_thread_setworking("running scenario");
523 38436 : msg = runScenario(c);
524 38436 : freeException(msg);
525 38436 : if (c->mode == FINISHCLIENT)
526 : break;
527 0 : resetScenario(c);
528 0 : } while (c->scenario && !GDKexiting());
529 38436 : } while (c->scenario && c->mode != FINISHCLIENT && !GDKexiting());
530 : }
531 38436 : 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 38436 : MT_exiting_thread();
536 : /*
537 : * At this stage we should clean out the MAL block
538 : */
539 38436 : if (c->backup) {
540 0 : assert(0);
541 : freeSymbol(c->backup);
542 : c->backup = 0;
543 : }
544 :
545 38436 : 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 38436 : MCcloseClient(c);
555 38436 : 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 314 : MALinitClient(Client c)
571 : {
572 314 : (void) c;
573 314 : return MAL_SUCCEED;
574 : }
575 :
576 : str
577 38468 : MALexitClient(Client c)
578 : {
579 38468 : if (c->glb && c->curprg->def && c->curprg->def->errors == MAL_SUCCEED)
580 38467 : garbageCollector(c, c->curprg->def, c->glb, TRUE);
581 38468 : c->mode = FINISHCLIENT;
582 38468 : if (c->backup) {
583 0 : assert(0);
584 : freeSymbol(c->backup);
585 : c->backup = NULL;
586 : }
587 : /* should be in the usermodule */
588 38468 : c->curprg = NULL;
589 38468 : if (c->usermodule) {
590 38468 : freeModule(c->usermodule);
591 38468 : c->usermodule = NULL;
592 : }
593 38468 : return NULL;
594 : }
595 :
596 : static str
597 10995 : MALreader(Client c)
598 : {
599 10995 : if (MCreadClient(c) > 0)
600 : return MAL_SUCCEED;
601 499 : MT_lock_set(&mal_contextLock);
602 499 : c->mode = FINISHCLIENT;
603 499 : MT_lock_unset(&mal_contextLock);
604 499 : if (c->fdin)
605 499 : 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 10496 : prepareMalBlk(MalBlkPtr mb, const char *s)
618 : {
619 10496 : int cnt = STMT_INCREMENT;
620 :
621 10496 : if (s && *s) {
622 24553 : while ((s = strchr(s + 1, '\n')) != NULL)
623 14057 : cnt++;
624 : }
625 10496 : cnt = (int) (cnt * 1.1);
626 10496 : return resizeMalBlk(mb, cnt);
627 : }
628 :
629 : str
630 10496 : MALparser(Client c)
631 : {
632 10496 : InstrPtr p;
633 10496 : str msg = MAL_SUCCEED;
634 :
635 10496 : assert(c->curprg->def->errors == NULL);
636 10496 : c->curprg->def->errors = 0;
637 :
638 10496 : if (prepareMalBlk(c->curprg->def, CURRENT(c)) < 0)
639 0 : throw(MAL, "mal.parser", "Failed to prepare");
640 10496 : parseMAL(c, c->curprg, 0, INT_MAX, 0);
641 :
642 : /* now the parsing is done we should advance the stream */
643 10496 : c->fdin->pos += c->yycur;
644 10496 : c->yycur = 0;
645 10496 : c->qryctx.starttime = GDKusec();
646 10496 : c->qryctx.endtime = c->querytimeout ? c->qryctx.starttime + c->querytimeout : 0;
647 :
648 : /* check for unfinished blocks */
649 10496 : if (!c->curprg->def->errors && c->blkmode)
650 : return MAL_SUCCEED;
651 : /* empty files should be skipped as well */
652 10241 : 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 9998 : p = getInstrPtr(c->curprg->def, 0);
659 9998 : 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 9998 : pushEndInstruction(c->curprg->def);
667 9998 : msg = chkProgram(c->usermodule, c->curprg->def);
668 9998 : 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 264931 : MALcommentsOnly(MalBlkPtr mb)
679 : {
680 264931 : int i;
681 :
682 265019 : for (i = 1; i < mb->stop; i++)
683 265019 : 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 10141 : MALoptimizer(Client c)
697 : {
698 10141 : str msg;
699 :
700 10141 : if (c->curprg->def->inlineProp)
701 : return MAL_SUCCEED;
702 : // only a signature statement can be skipped
703 10141 : if (c->curprg->def->stop == 1)
704 : return MAL_SUCCEED;
705 9951 : msg = optimizeMALBlock(c, c->curprg->def);
706 : /*
707 : if( msg == MAL_SUCCEED)
708 : msg = OPTmultiplexSimple(c, c->curprg->def);
709 : */
710 9951 : return msg;
711 : }
712 :
713 : static str
714 10740 : MALengine_(Client c)
715 : {
716 10740 : Symbol prg;
717 10740 : str msg = MAL_SUCCEED;
718 :
719 10995 : do {
720 10995 : if ((msg = MALreader(c)) != MAL_SUCCEED)
721 0 : return msg;
722 10995 : if (c->mode == FINISHCLIENT)
723 : return msg;
724 10496 : if ((msg = MALparser(c)) != MAL_SUCCEED)
725 100 : return msg;
726 10396 : } while (c->blkmode);
727 : /*
728 : if (c->blkmode)
729 : return MAL_SUCCEED;
730 : */
731 10141 : if ((msg = MALoptimizer(c)) != MAL_SUCCEED)
732 : return msg;
733 10141 : prg = c->curprg;
734 10141 : if (prg == NULL)
735 0 : throw(SYNTAX, "mal.engine", SYNTAX_SIGNATURE);
736 10141 : if (prg->def == NULL)
737 0 : throw(SYNTAX, "mal.engine", SYNTAX_SIGNATURE);
738 :
739 10141 : 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 10141 : if (prg->def->stop == 1 || MALcommentsOnly(prg->def))
747 190 : return 0; /* empty block */
748 9951 : if (c->glb) {
749 9951 : 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 9951 : c->glb->stktop = prg->def->vtop;
755 9951 : 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 9951 : if (c->glb) {
764 9951 : c->glb->pcup = 0;
765 9951 : c->glb->keepAlive = TRUE; /* no garbage collection */
766 : }
767 9951 : if (prg->def->errors == MAL_SUCCEED)
768 9951 : msg = (str) runMAL(c, prg->def, 0, c->glb);
769 9951 : 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 9951 : MSresetStack(c, prg->def, c->glb);
777 9951 : resetMalTypes(prg->def, 1);
778 9951 : if (c->glb) {
779 : /* for global stacks avoid reinitialization from this point */
780 9951 : c->glb->stkbot = prg->def->vtop;
781 : }
782 :
783 9951 : if (prg->def->errors)
784 0 : freeException(prg->def->errors);
785 9951 : prg->def->errors = NULL;
786 9951 : return msg;
787 : }
788 :
789 : void
790 10740 : MALengine(Client c)
791 : {
792 10740 : str msg = MALengine_(c);
793 10740 : 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 10740 : }
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 564274 : optimizeMALBlock(Client cntxt, MalBlkPtr mb)
820 : {
821 564274 : InstrPtr p;
822 564274 : int pc, oldstop;
823 564274 : str msg = MAL_SUCCEED;
824 564274 : int cnt = 0;
825 564274 : int actions = 0;
826 564274 : 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 564274 : if (mb->inlineProp)
831 : return 0;
832 :
833 564107 : mb->optimize = 0;
834 564107 : 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 564107 : if (mb->errors == 0 && mb->stop > 1) {
840 564107 : resetMalTypes(mb, mb->stop);
841 564106 : msg = chkTypes(cntxt->usermodule, mb, FALSE);
842 564101 : if (!msg)
843 564101 : msg = chkFlow(mb);
844 564105 : if (!msg)
845 564105 : msg = chkDeclarations(mb);
846 564104 : if (msg)
847 0 : return msg;
848 564104 : if (mb->errors != MAL_SUCCEED) {
849 0 : msg = mb->errors;
850 0 : mb->errors = MAL_SUCCEED;
851 0 : return msg;
852 : }
853 : }
854 :
855 564104 : oldstop = mb->stop;
856 41170263 : for (pc = 0; pc < mb->stop; pc++) {
857 40606156 : p = getInstrPtr(mb, pc);
858 40606156 : if (getModuleId(p) == optimizerRef && p->fcn && p->token != REMsymbol) {
859 13550031 : actions++;
860 13550031 : msg = (*(str (*)(Client, MalBlkPtr, MalStkPtr, InstrPtr)) p->fcn) (cntxt, mb, 0, p);
861 13550034 : if (mb->errors) {
862 0 : freeException(msg);
863 0 : msg = mb->errors;
864 0 : mb->errors = NULL;
865 : }
866 13550034 : 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 13550034 : 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 13550034 : pc += mb->stop - oldstop - 1;
887 13550034 : oldstop = mb->stop;
888 : }
889 : }
890 :
891 564107 : wrapup:
892 : /* Keep the total time spent on optimizing the plan for inspection */
893 564107 : if (actions > 0 && msg == MAL_SUCCEED) {
894 554244 : mb->optimize = GDKusec() - clk;
895 554244 : p = newStmt(mb, optimizerRef, totalRef);
896 554240 : if (p == NULL) {
897 0 : throw(MAL, "optimizer.MALoptimizer",
898 : SQLSTATE(HY013) MAL_MALLOC_FAIL);
899 : }
900 554240 : p->token = REMsymbol;
901 554240 : p = pushInt(mb, p, actions);
902 554238 : p = pushLng(mb, p, mb->optimize);
903 554239 : pushInstruction(mb, p);
904 : }
905 564102 : if (cnt >= mb->stop)
906 0 : throw(MAL, "optimizer.MALoptimizer", SQLSTATE(42000) OPTIMIZER_CYCLE);
907 : return msg;
908 : }
|