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 340 : malBootstrap(char *modules[], bool embedded, const char *initpasswd)
37 : {
38 340 : Client c;
39 340 : str msg = MAL_SUCCEED;
40 :
41 340 : c = MCinitClient(MAL_ADMIN, NULL, NULL);
42 340 : if (c == NULL) {
43 0 : throw(MAL, "malBootstrap", "Failed to initialize client");
44 : }
45 340 : MT_thread_set_qry_ctx(NULL);
46 340 : assert(c != NULL);
47 340 : c->curmodule = c->usermodule = userModule();
48 340 : if (c->usermodule == NULL) {
49 0 : MCcloseClient(c);
50 0 : throw(MAL, "malBootstrap", "Failed to initialize client MAL module");
51 : }
52 340 : if ((msg = defaultScenario(c))) {
53 0 : MCcloseClient(c);
54 0 : return msg;
55 : }
56 340 : if ((msg = MSinitClientPrg(c, "user", "main")) != MAL_SUCCEED) {
57 0 : MCcloseClient(c);
58 0 : return msg;
59 : }
60 :
61 340 : if (MCinitClientThread(c) < 0) {
62 0 : MCcloseClient(c);
63 0 : throw(MAL, "malBootstrap", "Failed to create client thread");
64 : }
65 340 : if ((msg = malIncludeModules(c, modules, 0, embedded, initpasswd)) != MAL_SUCCEED) {
66 0 : MCcloseClient(c);
67 0 : return msg;
68 : }
69 339 : MCcloseClient(c);
70 339 : 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 considerd 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 294773 : MSresetClientPrg(Client cntxt, const char *mod, const char *fcn)
89 : {
90 294773 : MalBlkPtr mb;
91 294773 : InstrPtr p;
92 :
93 294773 : mb = cntxt->curprg->def;
94 294773 : mb->stop = 1;
95 294773 : mb->errors = MAL_SUCCEED;
96 294773 : p = mb->stmt[0];
97 :
98 294773 : p->gc = 0;
99 294773 : p->retc = 1;
100 294773 : p->argc = 1;
101 294773 : p->argv[0] = 0;
102 :
103 294773 : setModuleId(p, mod);
104 294773 : setFunctionId(p, fcn);
105 294773 : if (findVariable(mb, fcn) < 0)
106 294763 : if ((p->argv[0] = newVariable(mb, fcn, strlen(fcn), TYPE_void)) < 0)
107 0 : throw(MAL, "resetClientPrg", SQLSTATE(HY013) MAL_MALLOC_FAIL);
108 :
109 294773 : setVarType(mb, findVariable(mb, fcn), TYPE_void);
110 294773 : return MAL_SUCCEED;
111 : }
112 :
113 : /*
114 : * Create a new container block
115 : */
116 :
117 : str
118 343999 : MSinitClientPrg(Client cntxt, const char *mod, const char *nme)
119 : {
120 343999 : int idx;
121 :
122 343999 : if (cntxt->curprg && idcmp(nme, cntxt->curprg->name) == 0)
123 294773 : return MSresetClientPrg(cntxt, putName(mod), putName(nme));
124 49226 : cntxt->curprg = newFunction(putName(mod), putName(nme), FUNCTIONsymbol);
125 49222 : if (cntxt->curprg == 0)
126 0 : throw(MAL, "initClientPrg", SQLSTATE(HY013) MAL_MALLOC_FAIL);
127 49222 : if ((idx = findVariable(cntxt->curprg->def, "main")) >= 0)
128 38307 : setVarType(cntxt->curprg->def, idx, TYPE_void);
129 49220 : insertSymbol(cntxt->usermodule, cntxt->curprg);
130 :
131 49218 : if (cntxt->glb == NULL)
132 38326 : cntxt->glb = newGlobalStack(MAXGLOBALS + cntxt->curprg->def->vsize);
133 49226 : if (cntxt->glb == NULL)
134 0 : throw(MAL, "initClientPrg", SQLSTATE(HY013) MAL_MALLOC_FAIL);
135 49226 : assert(cntxt->curprg->def != NULL);
136 49226 : 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 176 : exit_streams(bstream *fin, stream *fout)
150 : {
151 176 : if (fout && fout != GDKstdout) {
152 176 : mnstr_flush(fout, MNSTR_FLUSH_DATA);
153 176 : close_stream(fout);
154 : }
155 176 : if (fin)
156 176 : bstream_destroy(fin);
157 176 : }
158 :
159 : const char *mal_enableflag = "mal_for_all";
160 :
161 : static bool
162 1645 : is_exiting(void *data)
163 : {
164 1645 : (void) data;
165 1645 : return GDKexiting();
166 : }
167 :
168 : static str MSserveClient(Client cntxt);
169 :
170 :
171 : static inline void
172 9 : cleanUpScheduleClient(Client c, str *command, str *err)
173 : {
174 9 : if (c) {
175 9 : MCcloseClient(c);
176 : }
177 9 : if (command) {
178 3 : GDKfree(*command);
179 3 : *command = NULL;
180 : }
181 9 : if (err) {
182 9 : freeException(*err);
183 9 : *err = NULL;
184 : }
185 9 : }
186 :
187 :
188 : void
189 38141 : MSscheduleClient(str command, str challenge, bstream *fin, stream *fout,
190 : protocol_version protocol, size_t blocksize)
191 : {
192 38141 : char *user = command, *algo = NULL, *passwd = NULL, *lang = NULL,
193 38141 : *handshake_opts = NULL;
194 38141 : char *database = NULL, *s;
195 38141 : const char *dbname;
196 38141 : str msg = MAL_SUCCEED;
197 38141 : bool filetrans = false;
198 38141 : Client c;
199 :
200 38141 : MT_thread_set_qry_ctx(NULL);
201 :
202 : /* decode BIG/LIT:user:{cypher}passwordchal:lang:database: line */
203 :
204 : /* byte order */
205 38137 : s = strchr(user, ':');
206 38137 : if (s) {
207 37961 : *s = 0;
208 37961 : mnstr_set_bigendian(fin->s, strcmp(user, "BIG") == 0);
209 37947 : user = s + 1;
210 : } else {
211 176 : mnstr_printf(fout, "!incomplete challenge '%s'\n", user);
212 176 : exit_streams(fin, fout);
213 176 : GDKfree(command);
214 361 : return;
215 : }
216 :
217 : /* passwd */
218 37947 : s = strchr(user, ':');
219 37947 : if (s) {
220 37947 : *s = 0;
221 37947 : passwd = s + 1;
222 : /* decode algorithm, i.e. {plain}mypasswordchallenge */
223 37947 : 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 37947 : algo = passwd + 1;
230 37947 : s = strchr(algo, '}');
231 37947 : 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 37947 : *s = 0;
238 37947 : 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 37947 : s = strchr(passwd, ':');
248 37947 : if (s) {
249 37947 : *s = 0;
250 37947 : 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 37947 : s = strchr(lang, ':');
260 37947 : if (s) {
261 37947 : *s = 0;
262 37947 : database = s + 1;
263 : /* we can have stuff following, make it void */
264 37947 : s = strchr(database, ':');
265 37947 : if (s)
266 37947 : *s++ = 0;
267 : }
268 :
269 37947 : if (s && strncmp(s, "FILETRANS:", 10) == 0) {
270 37639 : s += 10;
271 37639 : filetrans = true;
272 308 : } else if (s && s[0] == ':') {
273 0 : s += 1;
274 0 : filetrans = false;
275 : }
276 :
277 37947 : if (s && strchr(s, ':') != NULL) {
278 37639 : handshake_opts = s;
279 37639 : s = strchr(s, ':');
280 37639 : *s++ = '\0';
281 : }
282 37947 : dbname = GDKgetenv("gdk_dbname");
283 37975 : if (database != NULL && database[0] != '\0' &&
284 36962 : 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 37975 : c = MCinitClient(0, fin, fout);
295 37974 : 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 37974 : c->filetrans = filetrans;
307 37974 : c->handshake_options = handshake_opts ? strdup(handshake_opts) : NULL;
308 : /* move this back !! */
309 37974 : if (c->usermodule == 0) {
310 37975 : c->curmodule = c->usermodule = userModule();
311 37975 : 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 37974 : 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 37974 : if (!GDKgetenv_isyes(mal_enableflag)
325 37975 : && strncasecmp("sql", lang, 3) != 0
326 498 : && 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 37972 : 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 37971 : c->username = GDKstrdup(user);
344 :
345 : /* NOTE ABOUT STARTING NEW THREADS
346 : * At this point we have conducted experiments (Jun 2012) with
347 : * reusing threads. The implementation used was a lockless array of
348 : * semaphores to wake up threads to do work. Experimentation on
349 : * Linux, Solaris and Darwin showed no significant improvements, in
350 : * most cases no improvements at all. Hence the following
351 : * conclusion: thread reuse doesn't save up on the costs of just
352 : * forking new threads. Since the latter means no difficulties of
353 : * properly maintaining a pool of threads and picking the workers
354 : * out of them, it is favourable just to start new threads on
355 : * demand. */
356 :
357 : /* fork a new thread to handle this client */
358 :
359 37965 : c->protocol = protocol;
360 37965 : c->blocksize = blocksize;
361 :
362 37965 : if (c->initClient) {
363 37965 : if ((msg = c->initClient(c, passwd, challenge, algo)) != MAL_SUCCEED) {
364 6 : mnstr_printf(fout, "!%s\n", msg);
365 6 : GDKfree(command);
366 6 : if (c->exitClient)
367 6 : c->exitClient(c);
368 6 : cleanUpScheduleClient(c, NULL, &msg);
369 6 : return;
370 : }
371 : }
372 37965 : GDKfree(command);
373 :
374 37965 : mnstr_settimeout(c->fdin->s, 50, is_exiting, NULL);
375 37964 : msg = MSserveClient(c);
376 37966 : if (msg != MAL_SUCCEED) {
377 0 : mnstr_printf(fout, "!could not serve client\n");
378 0 : exit_streams(fin, fout);
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 305120 : MSresetInstructions(MalBlkPtr mb, int start)
406 : {
407 305120 : int i;
408 305120 : InstrPtr p;
409 :
410 74525577 : for (i = start; i < mb->ssize; i++) {
411 74220171 : p = getInstrPtr(mb, i);
412 74220171 : if (p)
413 37384 : freeInstruction(p);
414 74220457 : mb->stmt[i] = NULL;
415 : }
416 305406 : mb->stop = start;
417 305406 : }
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 9916 : MSresetStack(Client cntxt, MalBlkPtr mb, MalStkPtr glb)
427 : {
428 9916 : InstrPtr sig = getInstrPtr(mb, 0);
429 9916 : int i, k = sig->argc;
430 :
431 9916 : if (mb->errors == MAL_SUCCEED) {
432 1250784 : for (i = sig->argc; i < mb->vtop; i++) {
433 1240868 : if (glb && i < glb->stktop && isTmpVar(mb, i) && !glb->keepTmps) {
434 7534 : if (mb->var[i].name)
435 2 : GDKfree(mb->var[i].name);
436 : /* clean stack entry */
437 7534 : garbageElement(cntxt, &glb->stk[i]);
438 7534 : glb->stk[i].vtype = TYPE_int;
439 7534 : glb->stk[i].len = 0;
440 7534 : glb->stk[i].val.pval = 0;
441 7534 : if (isVarConstant(mb, i))
442 4854 : garbageElement(cntxt, &mb->var[i].value);
443 : } else {
444 : /* compress the global variable list and stack */
445 1233334 : mb->var[k] = mb->var[i];
446 1233334 : glb->stk[k] = glb->stk[i];
447 1233334 : setVarUsed(mb, k);
448 1233334 : setVarInit(mb, k);
449 1233334 : if (i != k) {
450 70 : glb->stk[i].vtype = TYPE_int;
451 70 : glb->stk[i].len = 0;
452 70 : glb->stk[i].val.pval = 0;
453 70 : clrVarConstant(mb, i);
454 70 : clrVarCleanup(mb, i);
455 : }
456 1233334 : k++;
457 : }
458 : }
459 : }
460 9916 : assert(k <= mb->vsize);
461 9916 : mb->vtop = k;
462 9916 : }
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 37964 : MSserveClient(Client c)
489 : {
490 37964 : MalBlkPtr mb;
491 37964 : str msg = 0;
492 :
493 37964 : 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 37966 : mb = c->curprg->def;
502 37966 : if (c->glb == NULL)
503 0 : c->glb = newGlobalStack(MAXGLOBALS + mb->vsize);
504 37966 : if (c->glb == NULL) {
505 0 : MCcloseClient(c);
506 0 : throw(MAL, "serveClient", SQLSTATE(HY013) MAL_MALLOC_FAIL);
507 : } else {
508 37966 : c->glb->stktop = mb->vtop;
509 37966 : c->glb->blk = mb;
510 : }
511 :
512 37966 : if (c->scenario == 0)
513 0 : msg = defaultScenario(c);
514 37966 : if (msg) {
515 0 : MCcloseClient(c);
516 0 : return msg;
517 : } else {
518 37966 : do {
519 37966 : do {
520 37966 : MT_thread_setworking("running scenario");
521 37966 : msg = runScenario(c);
522 37965 : freeException(msg);
523 37965 : if (c->mode == FINISHCLIENT)
524 : break;
525 0 : resetScenario(c);
526 0 : } while (c->scenario && !GDKexiting());
527 37965 : } while (c->scenario && c->mode != FINISHCLIENT && !GDKexiting());
528 : }
529 37965 : 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 37965 : MT_exiting_thread();
534 : /*
535 : * At this stage we should clean out the MAL block
536 : */
537 37966 : if (c->backup) {
538 0 : assert(0);
539 : freeSymbol(c->backup);
540 : c->backup = 0;
541 : }
542 :
543 37966 : 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 37966 : MCcloseClient(c);
553 37966 : 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 314 : MALinitClient(Client c)
569 : {
570 314 : (void) c;
571 314 : return MAL_SUCCEED;
572 : }
573 :
574 : str
575 37995 : MALexitClient(Client c)
576 : {
577 37995 : if (c->glb && c->curprg->def && c->curprg->def->errors == MAL_SUCCEED)
578 37994 : garbageCollector(c, c->curprg->def, c->glb, TRUE);
579 37995 : c->mode = FINISHCLIENT;
580 37995 : if (c->backup) {
581 0 : assert(0);
582 : freeSymbol(c->backup);
583 : c->backup = NULL;
584 : }
585 : /* should be in the usermodule */
586 37995 : c->curprg = NULL;
587 37995 : if (c->usermodule) {
588 37995 : freeModule(c->usermodule);
589 37995 : c->usermodule = NULL;
590 : }
591 37995 : return NULL;
592 : }
593 :
594 : static str
595 10908 : MALreader(Client c)
596 : {
597 10908 : if (MCreadClient(c) > 0)
598 : return MAL_SUCCEED;
599 494 : MT_lock_set(&mal_contextLock);
600 494 : c->mode = FINISHCLIENT;
601 494 : MT_lock_unset(&mal_contextLock);
602 494 : if (c->fdin)
603 494 : 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 10414 : prepareMalBlk(MalBlkPtr mb, str s)
616 : {
617 10414 : int cnt = STMT_INCREMENT;
618 :
619 24340 : while (s) {
620 24340 : s = strchr(s, '\n');
621 24340 : if (s) {
622 13926 : s++;
623 13926 : cnt++;
624 : }
625 : }
626 10414 : cnt = (int) (cnt * 1.1);
627 10414 : return resizeMalBlk(mb, cnt);
628 : }
629 :
630 : str
631 10414 : MALparser(Client c)
632 : {
633 10414 : InstrPtr p;
634 10414 : str msg = MAL_SUCCEED;
635 :
636 10414 : assert(c->curprg->def->errors == NULL);
637 10414 : c->curprg->def->errors = 0;
638 :
639 10414 : if (prepareMalBlk(c->curprg->def, CURRENT(c)) < 0)
640 0 : throw(MAL, "mal.parser", "Failed to prepare");
641 10411 : parseMAL(c, c->curprg, 0, INT_MAX, 0);
642 :
643 : /* now the parsing is done we should advance the stream */
644 10409 : c->fdin->pos += c->yycur;
645 10409 : c->yycur = 0;
646 10409 : c->qryctx.starttime = GDKusec();
647 10410 : c->qryctx.endtime = c->querytimeout ? c->qryctx.starttime + c->querytimeout : 0;
648 :
649 : /* check for unfinished blocks */
650 10410 : if (!c->curprg->def->errors && c->blkmode)
651 : return MAL_SUCCEED;
652 : /* empty files should be skipped as well */
653 10155 : if (c->curprg->def->stop == 1) {
654 243 : if ((msg = c->curprg->def->errors))
655 53 : c->curprg->def->errors = 0;
656 243 : return msg;
657 : }
658 :
659 9912 : p = getInstrPtr(c->curprg->def, 0);
660 9912 : if (p->token != FUNCTIONsymbol) {
661 0 : msg = c->curprg->def->errors;
662 0 : c->curprg->def->errors = 0;
663 0 : MSresetStack(c, c->curprg->def, c->glb);
664 0 : resetMalTypes(c->curprg->def, 1);
665 0 : return msg;
666 : }
667 9912 : pushEndInstruction(c->curprg->def);
668 9915 : msg = chkProgram(c->usermodule, c->curprg->def);
669 9909 : if (msg != MAL_SUCCEED || (msg = c->curprg->def->errors)) {
670 46 : c->curprg->def->errors = 0;
671 46 : MSresetStack(c, c->curprg->def, c->glb);
672 47 : resetMalTypes(c->curprg->def, 1);
673 47 : return msg;
674 : }
675 : return MAL_SUCCEED;
676 : }
677 :
678 : int
679 256704 : MALcommentsOnly(MalBlkPtr mb)
680 : {
681 256704 : int i;
682 :
683 256792 : for (i = 1; i < mb->stop; i++)
684 257385 : if (mb->stmt[i]->token != REMsymbol)
685 : return 0;
686 : return 1;
687 : }
688 :
689 : /*
690 : * The default MAL optimizer includes a final call to
691 : * the multiplex expander.
692 : * We should take care of functions marked as 'inline',
693 : * because they should be kept in raw form.
694 : * Their optimization takes place after inlining.
695 : */
696 : static str
697 10050 : MALoptimizer(Client c)
698 : {
699 10050 : str msg;
700 :
701 10050 : if (c->curprg->def->inlineProp)
702 : return MAL_SUCCEED;
703 : // only a signature statement can be skipped
704 10050 : if (c->curprg->def->stop == 1)
705 : return MAL_SUCCEED;
706 9860 : msg = optimizeMALBlock(c, c->curprg->def);
707 : /*
708 : if( msg == MAL_SUCCEED)
709 : msg = OPTmultiplexSimple(c, c->curprg->def);
710 : */
711 9860 : return msg;
712 : }
713 :
714 : static str
715 10653 : MALengine_(Client c)
716 : {
717 10653 : Symbol prg;
718 10653 : str msg = MAL_SUCCEED;
719 :
720 10905 : do {
721 10905 : if ((msg = MALreader(c)) != MAL_SUCCEED)
722 0 : return msg;
723 10908 : if (c->mode == FINISHCLIENT)
724 : return msg;
725 10414 : if ((msg = MALparser(c)) != MAL_SUCCEED)
726 100 : return msg;
727 10303 : } while (c->blkmode);
728 : /*
729 : if (c->blkmode)
730 : return MAL_SUCCEED;
731 : */
732 10051 : if ((msg = MALoptimizer(c)) != MAL_SUCCEED)
733 : return msg;
734 10053 : prg = c->curprg;
735 10053 : if (prg == NULL)
736 0 : throw(SYNTAX, "mal.engine", SYNTAX_SIGNATURE);
737 10053 : if (prg->def == NULL)
738 0 : throw(SYNTAX, "mal.engine", SYNTAX_SIGNATURE);
739 :
740 10053 : if (prg->def->errors != MAL_SUCCEED) {
741 0 : msg = prg->def->errors;
742 0 : prg->def->errors = NULL;
743 0 : MSresetStack(c, c->curprg->def, c->glb);
744 0 : resetMalTypes(c->curprg->def, 1);
745 0 : return msg;
746 : }
747 10053 : if (prg->def->stop == 1 || MALcommentsOnly(prg->def))
748 190 : return 0; /* empty block */
749 9867 : if (c->glb) {
750 9867 : if (prg->def && c->glb->stksize < prg->def->vsize) {
751 4 : c->glb = reallocGlobalStack(c->glb, prg->def->vsize);
752 4 : if (c->glb == NULL)
753 0 : throw(MAL, "mal.engine", SQLSTATE(HY013) MAL_MALLOC_FAIL);
754 : }
755 9867 : c->glb->stktop = prg->def->vtop;
756 9867 : c->glb->blk = prg->def;
757 : }
758 :
759 : /*
760 : * In interactive mode we should avoid early garbage collection of values.
761 : * This can be controlled by the clean up control at the instruction level
762 : * and marking all non-temporary variables as being (potentially) used.
763 : */
764 9867 : if (c->glb) {
765 9867 : c->glb->pcup = 0;
766 9867 : c->glb->keepAlive = TRUE; /* no garbage collection */
767 : }
768 9867 : if (prg->def->errors == MAL_SUCCEED)
769 9864 : msg = (str) runMAL(c, prg->def, 0, c->glb);
770 9872 : if (msg) {
771 : /* ignore "internal" exceptions */
772 32 : if (strstr(msg, "client.quit")) {
773 0 : freeException(msg);
774 0 : msg = MAL_SUCCEED;
775 : }
776 : }
777 9872 : MSresetStack(c, prg->def, c->glb);
778 9869 : resetMalTypes(prg->def, 1);
779 9869 : if (c->glb) {
780 : /* for global stacks avoid reinitialization from this point */
781 9869 : c->glb->stkbot = prg->def->vtop;
782 : }
783 :
784 9869 : if (prg->def->errors)
785 0 : freeException(prg->def->errors);
786 9869 : prg->def->errors = NULL;
787 9869 : return msg;
788 : }
789 :
790 : void
791 10653 : MALengine(Client c)
792 : {
793 10653 : str msg = MALengine_(c);
794 10653 : if (msg) {
795 : /* don't print exception decoration, just the message */
796 322 : char *n = NULL;
797 : char *o = msg;
798 322 : while ((n = strchr(o, '\n')) != NULL) {
799 193 : if (*o == '!')
800 0 : o++;
801 193 : mnstr_printf(c->fdout, "!%.*s\n", (int) (n - o), o);
802 193 : o = ++n;
803 : }
804 129 : if (*o != 0) {
805 33 : if (*o == '!')
806 4 : o++;
807 33 : mnstr_printf(c->fdout, "!%s\n", o);
808 : }
809 129 : freeException(msg);
810 : }
811 10653 : }
812 :
813 : /* Hypothetical, optimizers may massage the plan in such a way
814 : * that multiple passes are needed.
815 : * However, the current SQL driven approach only expects a single
816 : * non-repeating pipeline of optimizer steps stored at the end of the MAL block.
817 : * A single scan forward over the MAL plan is assumed.
818 : */
819 : str
820 558448 : optimizeMALBlock(Client cntxt, MalBlkPtr mb)
821 : {
822 558448 : InstrPtr p;
823 558448 : int pc, oldstop;
824 558448 : str msg = MAL_SUCCEED;
825 558448 : int cnt = 0;
826 558448 : int actions = 0;
827 558448 : lng clk = GDKusec();
828 :
829 : /* assume the type and flow have been checked already */
830 : /* SQL functions intended to be inlined should not be optimized */
831 558500 : if (mb->inlineProp)
832 : return 0;
833 :
834 558335 : mb->optimize = 0;
835 558335 : if (mb->errors)
836 0 : throw(MAL, "optimizer.MALoptimizer",
837 : SQLSTATE(42000) "Start with inconsistent MAL plan");
838 :
839 : // strong defense line, assure that MAL plan is initially correct
840 558335 : if (mb->errors == 0 && mb->stop > 1) {
841 558217 : resetMalTypes(mb, mb->stop);
842 558309 : msg = chkTypes(cntxt->usermodule, mb, FALSE);
843 557174 : if (!msg)
844 557378 : msg = chkFlow(mb);
845 558122 : if (!msg)
846 558180 : msg = chkDeclarations(mb);
847 557892 : if (msg)
848 0 : return msg;
849 557834 : if (mb->errors != MAL_SUCCEED) {
850 0 : msg = mb->errors;
851 0 : mb->errors = MAL_SUCCEED;
852 0 : return msg;
853 : }
854 : }
855 :
856 557952 : oldstop = mb->stop;
857 40224553 : for (pc = 0; pc < mb->stop; pc++) {
858 39666267 : p = getInstrPtr(mb, pc);
859 39666267 : if (getModuleId(p) == optimizerRef && p->fcn && p->token != REMsymbol) {
860 13368298 : actions++;
861 13368298 : msg = (*(str (*)(Client, MalBlkPtr, MalStkPtr, InstrPtr)) p->fcn) (cntxt, mb, 0, p);
862 13368632 : if (mb->errors) {
863 0 : freeException(msg);
864 0 : msg = mb->errors;
865 0 : mb->errors = NULL;
866 : }
867 13368632 : if (msg) {
868 0 : str place = getExceptionPlace(msg);
869 0 : str nmsg = NULL;
870 0 : if (place) {
871 0 : nmsg = createException(getExceptionType(msg), place, "%s",
872 : getExceptionMessageAndState(msg));
873 0 : GDKfree(place);
874 : }
875 0 : if (nmsg) {
876 0 : freeException(msg);
877 0 : msg = nmsg;
878 : }
879 0 : goto wrapup;
880 : }
881 13368632 : if (cntxt->mode == FINISHCLIENT) {
882 0 : mb->optimize = GDKusec() - clk;
883 0 : throw(MAL, "optimizeMALBlock",
884 : SQLSTATE(42000) "prematurely stopped client");
885 : }
886 : /* the MAL block may have changed */
887 13368632 : pc += mb->stop - oldstop - 1;
888 13368632 : oldstop = mb->stop;
889 : }
890 : }
891 :
892 558286 : wrapup:
893 : /* Keep the total time spent on optimizing the plan for inspection */
894 558286 : if (actions > 0 && msg == MAL_SUCCEED) {
895 548536 : mb->optimize = GDKusec() - clk;
896 548555 : p = newStmt(mb, optimizerRef, totalRef);
897 548587 : if (p == NULL) {
898 0 : throw(MAL, "optimizer.MALoptimizer",
899 : SQLSTATE(HY013) MAL_MALLOC_FAIL);
900 : }
901 548587 : p->token = REMsymbol;
902 548587 : p = pushInt(mb, p, actions);
903 548566 : p = pushLng(mb, p, mb->optimize);
904 548536 : pushInstruction(mb, p);
905 : }
906 558242 : if (cnt >= mb->stop)
907 0 : throw(MAL, "optimizer.MALoptimizer", SQLSTATE(42000) OPTIMIZER_CYCLE);
908 : return msg;
909 : }
|