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