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 : /*
14 : * (authors) N. Nes, M.L. Kersten
15 : * The SQL scenario implementation is a derivative of the MAL session scenario.
16 : *
17 : */
18 : /*
19 : * Before we are can process SQL statements the global catalog
20 : * should be initialized. Thereafter, each time a client enters
21 : * we update its context descriptor to denote an SQL scenario.
22 : */
23 : #include "monetdb_config.h"
24 : #include "mal_backend.h"
25 : #include "sql_scenario.h"
26 : #include "sql_result.h"
27 : #include "sql_gencode.h"
28 : #include "sql_optimizer.h"
29 : #include "sql_assert.h"
30 : #include "sql_execute.h"
31 : #include "sql_env.h"
32 : #include "sql_mvc.h"
33 : #include "sql_user.h"
34 : #include "sql_datetime.h"
35 : #include "sql_import.h"
36 : #include "mal.h"
37 : #include "mal_instruction.h"
38 : #include "mal_interpreter.h"
39 : #include "mal_parser.h"
40 : #include "mal_builder.h"
41 : #include "mal_namespace.h"
42 : #include "mal_linker.h"
43 : #include "mal_scenario.h"
44 : #include "mal_authorize.h"
45 : #include "mcrypt.h"
46 : #include "mutils.h"
47 : #include "bat5.h"
48 : #include "msabaoth.h"
49 : #include "gdk_time.h"
50 : #include "optimizer.h"
51 : #include "opt_prelude.h"
52 : #include "opt_pipes.h"
53 : #include "opt_mitosis.h"
54 : #include <unistd.h>
55 : #include "sql_upgrades.h"
56 : #include "rel_semantic.h"
57 : #include "rel_rel.h"
58 :
59 : #define MAX_SQL_MODULES 128
60 : static int sql_modules = 0;
61 : static struct sql_module {
62 : const char *name;
63 : const unsigned char *code;
64 : } sql_module[MAX_SQL_MODULES];
65 :
66 : static int
67 24523 : sql_module_compare(const void *a, const void *b)
68 : {
69 24523 : const struct sql_module *l = a, *r = b;
70 24523 : return strcmp(l->name, r->name);
71 : }
72 :
73 : void
74 12903 : sql_register(const char *name, const unsigned char *code)
75 : {
76 12903 : assert (sql_modules < MAX_SQL_MODULES);
77 12903 : sql_module[sql_modules].name = name;
78 12903 : sql_module[sql_modules].code = code;
79 12903 : sql_modules++;
80 12903 : }
81 :
82 : static sql_store SQLstore = NULL;
83 : int SQLdebug = 0;
84 : static const char *sqlinit = NULL;
85 : static MT_Lock sql_contextLock = MT_LOCK_INITIALIZER(sql_contextLock);
86 :
87 : static str SQLinit(Client c, const char *initpasswd);
88 : static str master_password = NULL;
89 :
90 : static void
91 114 : SQLprintinfo(void)
92 : {
93 : /* we need to start printing SQL info here... */
94 114 : store_printinfo(SQLstore);
95 114 : }
96 :
97 : str
98 : //SQLprelude(void *ret)
99 341 : SQLprelude(Client cntxt, MalBlkPtr mb, MalStkPtr stk, InstrPtr pci)
100 : {
101 341 : str tmp;
102 341 : Scenario ms, s = getFreeScenario();
103 : /* HACK ALERT: temporarily use sqlcontext to pass the initial
104 : * password to the prelude function */
105 341 : const char *initpasswd = cntxt->sqlcontext;
106 341 : cntxt->sqlcontext = NULL;
107 : /* HACK ALERT: use mb (MalBlkPtr) to pass revision string in order
108 : * to check that in the callee */
109 341 : if (mb) {
110 341 : const char *caller_revision = (const char *) (void *) mb;
111 341 : const char *p = mercurial_revision();
112 341 : if (p && strcmp(p, caller_revision) != 0) {
113 0 : throw(MAL, "sql.start", "incompatible versions: caller is %s, GDK is %s\n", caller_revision, p);
114 : }
115 : }
116 :
117 341 : (void) stk;
118 341 : (void) pci;
119 341 : if (!s)
120 0 : throw(MAL, "sql.start", SQLSTATE(42000) "out of scenario slots");
121 341 : sqlinit = GDKgetenv("sqlinit");
122 341 : *s = (struct SCENARIO) {
123 : .name = "S_Q_L",
124 : .language = "sql",
125 : .initClient = "SQLinitClient",
126 : .initClientCmd = SQLinitClient,
127 : .exitClient = "SQLexitClient",
128 : .exitClientCmd = SQLexitClient,
129 : .engine = "SQLengine",
130 : .engineCmd = SQLengine,
131 : };
132 341 : ms = getFreeScenario();
133 341 : if (!ms)
134 0 : throw(MAL, "sql.start", SQLSTATE(42000) "out of scenario slots");
135 :
136 341 : *ms = (struct SCENARIO) {
137 : .name = "M_S_Q_L",
138 : .language = "msql",
139 : .initClient = "SQLinitClientFromMAL",
140 : .initClientCmd = SQLinitClientFromMAL,
141 : .exitClient = "SQLexitClient",
142 : .exitClientCmd = SQLexitClient,
143 : .engine = "MALengine",
144 : .engineCmd = MALengine,
145 : };
146 :
147 341 : tmp = SQLinit(cntxt, initpasswd);
148 341 : if (tmp != MAL_SUCCEED) {
149 1 : TRC_CRITICAL(SQL_PARSER, "Fatal error during initialization: %s\n", tmp);
150 1 : if (!GDKembedded()) {
151 1 : freeException(tmp);
152 1 : if ((tmp = GDKerrbuf) && *tmp)
153 0 : TRC_CRITICAL(SQL_PARSER, SQLSTATE(42000) "GDK reported: %s\n", tmp);
154 1 : fflush(stderr);
155 1 : exit(1);
156 : } else {
157 : return tmp;
158 : }
159 : }
160 340 : if (!GDKembedded()) {
161 328 : fprintf(stdout, "# MonetDB/SQL module loaded\n");
162 328 : fflush(stdout); /* make merovingian see this *now* */
163 : }
164 340 : GDKprintinforegister(SQLprintinfo);
165 340 : if (GDKinmemory(0) || GDKembedded()) {
166 12 : s->name = "sql";
167 12 : ms->name = "msql";
168 12 : return MAL_SUCCEED;
169 : }
170 : /* only register availability of scenarios AFTER we are inited! */
171 328 : s->name = "sql";
172 328 : tmp = msab_marchScenario(s->name);
173 328 : if (tmp != NULL) {
174 0 : char *err = createException(MAL, "sql.start", "%s", tmp);
175 0 : free(tmp);
176 0 : return err;
177 : }
178 328 : ms->name = "msql";
179 328 : tmp = msab_marchScenario(ms->name);
180 328 : if (tmp != NULL) {
181 0 : char *err = createException(MAL, "sql.start", "%s", tmp);
182 0 : free(tmp);
183 0 : return err;
184 : }
185 : return MAL_SUCCEED;
186 : }
187 :
188 : static str
189 339 : SQLexit(Client c)
190 : {
191 339 : (void) c; /* not used */
192 339 : MT_lock_set(&sql_contextLock);
193 339 : if (SQLstore) {
194 339 : mvc_exit(SQLstore);
195 339 : SQLstore = NULL;
196 : }
197 339 : MT_lock_unset(&sql_contextLock);
198 339 : return MAL_SUCCEED;
199 : }
200 :
201 : str
202 339 : SQLepilogue(void *ret)
203 : {
204 339 : const char *s = "sql", *m = "msql";
205 339 : char *msg;
206 :
207 339 : (void) ret;
208 339 : msg = SQLexit(NULL);
209 339 : freeException(msg);
210 : /* this function is never called, but for the style of it, we clean
211 : * up our own mess */
212 339 : if (!GDKinmemory(0) && !GDKembedded()) {
213 328 : str res = msab_retreatScenario(m);
214 328 : if (!res)
215 328 : res = msab_retreatScenario(s);
216 328 : if (res != NULL) {
217 0 : char *err = createException(MAL, "sql.epilogue", "%s", res);
218 0 : free(res);
219 0 : return err;
220 : }
221 : }
222 : /* return scenarios */
223 339 : Scenario sc = findScenario(s);
224 339 : if (sc)
225 339 : sc->name = NULL;
226 339 : sc = findScenario(m);
227 339 : if (sc)
228 339 : sc->name = NULL;
229 : return MAL_SUCCEED;
230 : }
231 :
232 :
233 : static str
234 155 : SQLexecPostLoginTriggers(Client c)
235 : {
236 155 : str msg = MAL_SUCCEED;
237 155 : backend *be = (backend *) c->sqlcontext;
238 155 : if (be) {
239 155 : mvc *m = be->mvc;
240 155 : sql_trans *tr = m->session->tr;
241 155 : int active = tr->active;
242 155 : if (active || mvc_trans(m) == 0) {
243 155 : sql_schema *sys = find_sql_schema(tr, "sys");
244 155 : struct os_iter oi;
245 : // triggers not related to table should have been loaded in sys
246 155 : os_iterator(&oi, sys->triggers, tr, NULL);
247 300 : for (sql_base *b = oi_next(&oi); b && msg == MAL_SUCCEED; b = oi_next(&oi)) {
248 145 : sql_trigger *t = (sql_trigger*) b;
249 145 : if (t->event == LOGIN_EVENT) {
250 1 : const char *stmt = t->statement;
251 1 : sql_rel *r = NULL;
252 : // cache state
253 1 : int oldvtop = c->curprg->def->vtop;
254 1 : int oldstop = c->curprg->def->stop;
255 1 : Symbol curprg = c->curprg;
256 1 : allocator *sa = m->sa;
257 :
258 1 : if (!(m->sa = sa_create(m->pa))) {
259 0 : m->sa = sa;
260 0 : throw(SQL, "sql.SQLexecPostLoginTriggers", SQLSTATE(HY013) MAL_MALLOC_FAIL);
261 : }
262 1 : r = rel_parse(m, sys, stmt, m_deps);
263 1 : if (r)
264 1 : r = sql_processrelation(m, r, 0, 0, 0, 0);
265 1 : if (!r) {
266 0 : sa_destroy(m->sa);
267 0 : m->sa = sa;
268 0 : if (strlen(m->errstr) > 6 && m->errstr[5] == '!')
269 0 : throw(SQL, "sql.SQLexecPostLoginTriggers", "%s", m->errstr);
270 : else
271 0 : throw(SQL, "sql.SQLexecPostLoginTriggers", SQLSTATE(42000) "%s", m->errstr);
272 : }
273 :
274 1 : setVarType(c->curprg->def, 0, 0);
275 1 : if (backend_dumpstmt(be, c->curprg->def, r, 1, 1, NULL) < 0) {
276 0 : freeVariables(c, c->curprg->def, NULL, oldvtop);
277 0 : c->curprg = curprg;
278 0 : sa_destroy(m->sa);
279 0 : m->sa = sa;
280 0 : throw(SQL, "sql.SQLexecPostLoginTriggers", SQLSTATE(4200) "%s", "generating MAL failed");
281 : }
282 :
283 1 : msg = SQLoptimizeQuery(c, c->curprg->def);
284 :
285 1 : stream *out = be->out;
286 1 : be->out = NULL; /* no output stream */
287 1 : if (!msg)
288 1 : msg = SQLrun(c,m);
289 :
290 : // restore previous state
291 1 : be->out = out;
292 1 : MSresetInstructions(c->curprg->def, oldstop);
293 1 : freeVariables(c, c->curprg->def, NULL, oldvtop);
294 1 : sqlcleanup(be, 0);
295 1 : c->curprg = curprg;
296 1 : sa_destroy(m->sa);
297 1 : m->sa = sa;
298 : }
299 : }
300 :
301 155 : if (!active)
302 155 : sql_trans_end(m->session, SQL_OK);
303 : }
304 : }
305 : return msg;
306 : }
307 :
308 : static str
309 37658 : userCheckCredentials( mvc *m, Client c, const char *pwhash, const char *challenge, const char *algo)
310 : {
311 37658 : oid uid = getUserOIDByName(m, c->username);
312 :
313 37651 : if (strNil(pwhash))
314 0 : throw(INVCRED, "checkCredentials", INVCRED_INVALID_USER " '%s'", c->username);
315 37651 : str passValue = getUserPassword(m, uid);
316 37658 : if (strNil(passValue))
317 4 : throw(INVCRED, "checkCredentials", INVCRED_INVALID_USER " '%s'", c->username);
318 : /* find the corresponding password to the user */
319 :
320 37654 : str pwd = NULL;
321 37654 : str msg = AUTHdecypherValue(&pwd, passValue);
322 37654 : GDKfree(passValue);
323 37654 : if (msg)
324 : return msg;
325 :
326 : /* generate the hash as the client should have done */
327 37654 : str hash = mcrypt_hashPassword(algo, pwd, challenge);
328 37654 : GDKfree(pwd);
329 37649 : if(!hash)
330 0 : throw(MAL, "checkCredentials", "hash '%s' backend not found", algo);
331 :
332 : /* and now we have it, compare it to what was given to us */
333 37649 : if (strcmp(pwhash, hash) == 0) {
334 37647 : free(hash);
335 37647 : c->user = uid;
336 37647 : return MAL_SUCCEED;
337 : }
338 2 : free(hash);
339 :
340 : /* special case: users whose name starts with '.' can authenticate using
341 : * the temporary master password.
342 : */
343 2 : if (c->username[0] == '.' && master_password != NULL && master_password[0] != '\0') {
344 : // first encrypt the master password as if we've just found it
345 : // in the password store
346 0 : str encrypted = mcrypt_BackendSum(master_password, strlen(master_password));
347 0 : if (encrypted == NULL)
348 0 : throw(MAL, "checkCredentials", SQLSTATE(HY013) MAL_MALLOC_FAIL);
349 0 : hash = mcrypt_hashPassword(algo, encrypted, challenge);
350 0 : free(encrypted);
351 0 : if (hash && strcmp(pwhash, hash) == 0) {
352 0 : free(hash);
353 0 : c->user = uid;
354 0 : return(MAL_SUCCEED);
355 : }
356 0 : free(hash);
357 : }
358 :
359 : /* of course we DO NOT print the password here */
360 2 : throw(INVCRED, "checkCredentials", INVCRED_INVALID_USER " '%s'", c->username);
361 : }
362 :
363 : static char*
364 38030 : SQLprepareClient(Client c, const char *pwhash, const char *challenge, const char *algo)
365 : {
366 38030 : mvc *m = NULL;
367 38030 : backend *be = NULL;
368 38030 : str msg = MAL_SUCCEED;
369 :
370 38030 : if (c->sqlcontext == 0) {
371 38030 : allocator *sa = sa_create(NULL);
372 38030 : if (sa == NULL) {
373 0 : msg = createException(SQL,"sql.initClient", SQLSTATE(HY013) MAL_MALLOC_FAIL);
374 0 : goto bailout2;
375 : }
376 38030 : m = mvc_create(SQLstore, sa, c->idx, SQLdebug, c->fdin, c->fdout);
377 38030 : if (m == NULL) {
378 0 : msg = createException(SQL,"sql.initClient", SQLSTATE(HY013) MAL_MALLOC_FAIL);
379 0 : goto bailout2;
380 : }
381 38030 : if (c->scenario && strcmp(c->scenario, "msql") == 0)
382 181 : m->reply_size = -1;
383 38030 : be = (void *) backend_create(m, c);
384 38030 : if ( be == NULL) {
385 0 : mvc_destroy(m);
386 0 : msg = createException(SQL,"sql.initClient", SQLSTATE(HY013) MAL_MALLOC_FAIL);
387 0 : goto bailout2;
388 : }
389 : } else {
390 0 : assert(0);
391 : }
392 38030 : MT_lock_unset(&sql_contextLock);
393 38030 : if (c->username && pwhash) {
394 :
395 37658 : if (mvc_trans(m) < 0) {
396 : // we have -1 here
397 0 : MT_lock_set(&sql_contextLock);
398 0 : throw(INVCRED, "checkCredentials", INVCRED_INVALID_USER " '%s'", c->username);
399 : }
400 :
401 37658 : msg = userCheckCredentials( m, c, pwhash, challenge, algo);
402 37654 : if (msg)
403 6 : goto bailout1;
404 37648 : if (!GDKinmemory(0) && !GDKembedded()) {
405 37650 : sabdb *stats = NULL;
406 37650 : bool locked = false;
407 37650 : char *err = msab_getMyStatus(&stats);
408 37646 : if (err || stats == NULL)
409 0 : free(err);
410 : else
411 37646 : locked = stats->locked;
412 37646 : msab_freeStatus(&stats);
413 37636 : if (locked) {
414 0 : if (c->user == 0) {
415 0 : mnstr_printf(c->fdout, "#server is running in "
416 : "maintenance mode\n");
417 : } else {
418 0 : msg = createException(SQL,"sql.initClient", SQLSTATE(42000) "server is running in maintenance mode, please try again later\n");
419 0 : goto bailout1;
420 : }
421 : }
422 : }
423 :
424 37634 : switch (monet5_user_set_def_schema(m, c->user, c->username)) {
425 0 : case -1:
426 0 : msg = createException(SQL,"sql.initClient", SQLSTATE(HY013) MAL_MALLOC_FAIL);
427 0 : goto bailout1;
428 0 : case -2:
429 0 : msg = createException(SQL,"sql.initClient", SQLSTATE(42000) "The user was not found in the database, this session is going to terminate");
430 0 : goto bailout1;
431 0 : case -3:
432 0 : msg = createException(SQL,"sql.initClient", SQLSTATE(42000) "The user's default schema was not found, this session is going to terminate");
433 0 : goto bailout1;
434 0 : case -4:
435 0 : msg = createException(SQL,"sql.initClient", SQLSTATE(42000) "The user's default role was not found, this session is going to terminate");
436 0 : goto bailout1;
437 : default:
438 37652 : break;
439 : }
440 37652 : if (monet5_user_get_limits(m, m->user_id, &c->maxmem, &c->maxworkers) == 0) {
441 37652 : c->qryctx.maxmem = (ATOMIC_BASE_TYPE) (c->maxmem > 0 ? c->maxmem : 0);
442 : } else {
443 0 : c->maxmem = 0;
444 0 : c->qryctx.maxmem = 0;
445 0 : c->maxworkers = 0;
446 : }
447 37652 : if (c->memorylimit > 0 && c->qryctx.maxmem > ((ATOMIC_BASE_TYPE) c->memorylimit << 20))
448 0 : c->qryctx.maxmem = (ATOMIC_BASE_TYPE) c->memorylimit << 20;
449 37652 : mvc_rollback(m, 0, NULL, false);
450 : }
451 :
452 38023 : if (c->handshake_options) {
453 37650 : char *strtok_state = NULL;
454 37650 : char *tok = strtok_r(c->handshake_options, ",", &strtok_state);
455 189613 : while (tok != NULL) {
456 151963 : int value;
457 151963 : if (sscanf(tok, "auto_commit=%d", &value) == 1) {
458 37649 : bool auto_commit= value != 0;
459 37649 : m->session->auto_commit = auto_commit;
460 37649 : m->session->ac_on_commit = auto_commit;
461 114314 : } else if (sscanf(tok, "reply_size=%d", &value) == 1) {
462 37648 : if (value < -1) {
463 0 : msg = createException(SQL, "SQLprepareClient", SQLSTATE(42000) "Reply_size cannot be negative");
464 0 : goto bailout1;
465 : }
466 37648 : m->reply_size = value;
467 76666 : } else if (sscanf(tok, "size_header=%d", &value) == 1) {
468 37649 : be->sizeheader = value != 0;
469 39017 : } else if (sscanf(tok, "columnar_protocol=%d", &value) == 1) {
470 2734 : c->protocol = (value != 0) ? PROTOCOL_COLUMNAR : PROTOCOL_9;
471 37650 : } else if (sscanf(tok, "time_zone=%d", &value) == 1) {
472 37650 : sql_schema *s = mvc_bind_schema(m, "sys");
473 37650 : sql_var *var = find_global_var(m, s, "current_timezone");
474 37650 : ValRecord val;
475 37650 : VALinit(&val, TYPE_lng, &(lng){1000 * value});
476 37646 : if ((msg = sql_update_var(m, s, "current_timezone", &val)))
477 0 : goto bailout1;
478 37645 : sqlvar_set(var, &val);
479 : } else {
480 0 : msg = createException(SQL, "SQLprepareClient", SQLSTATE(42000) "unexpected handshake option: %s", tok);
481 0 : goto bailout1;
482 : }
483 :
484 151962 : tok = strtok_r(NULL, ",", &strtok_state);
485 : }
486 : }
487 :
488 :
489 373 : bailout1:
490 38027 : if (m->session->tr->active)
491 6 : mvc_rollback(m, 0, NULL, false);
492 38027 : MT_lock_set(&sql_contextLock);
493 38030 : bailout2:
494 : /* expect SQL text first */
495 38030 : if (be)
496 38030 : be->language = 'S';
497 : /* Set state, this indicates an initialized client scenario */
498 38030 : c->sqlcontext = be;
499 38030 : if (msg)
500 6 : c->mode = FINISHCLIENT;
501 : return msg;
502 : }
503 :
504 : str
505 38030 : SQLresetClient(Client c)
506 : {
507 38030 : str msg = MAL_SUCCEED, other = MAL_SUCCEED;
508 :
509 38030 : if (c->sqlcontext == NULL)
510 0 : throw(SQL, "SQLexitClient", SQLSTATE(42000) "MVC catalogue not available");
511 38030 : if (c->sqlcontext) {
512 38030 : allocator *pa = NULL;
513 38030 : backend *be = c->sqlcontext;
514 38030 : mvc *m = be->mvc;
515 :
516 38030 : assert(m->session);
517 38030 : if (m->session->auto_commit && m->session->tr->active) {
518 11 : if (mvc_status(m) >= 0)
519 11 : msg = mvc_commit(m, 0, NULL, false);
520 : }
521 38030 : if (m->session->tr->active)
522 1027 : other = mvc_rollback(m, 0, NULL, false);
523 :
524 38030 : res_tables_destroy(be->results);
525 38030 : be->results = NULL;
526 :
527 38030 : pa = m->pa;
528 38030 : mvc_destroy(m);
529 38030 : backend_destroy(be);
530 38030 : c->sqlcontext = NULL;
531 38030 : c->query = NULL;
532 38030 : sa_destroy(pa);
533 : }
534 38030 : if (other && !msg)
535 : msg = other;
536 38030 : else if (other && msg)
537 0 : freeException(other);
538 : return msg;
539 : }
540 :
541 : MT_Id sqllogthread;
542 :
543 : static str
544 341 : SQLinit(Client c, const char *initpasswd)
545 : {
546 341 : const char *debug_str = GDKgetenv("sql_debug");
547 341 : char *msg = MAL_SUCCEED, *other = MAL_SUCCEED;
548 341 : bool readonly = GDKgetenv_isyes("gdk_readonly");
549 341 : bool single_user = GDKgetenv_isyes("gdk_single_user");
550 341 : static int maybeupgrade = 1;
551 341 : backend *be = NULL;
552 341 : mvc *m = NULL;
553 341 : const char *opt_pipe;
554 :
555 341 : master_password = NULL;
556 341 : if (!GDKembedded() && !GDKinmemory(0)) {
557 329 : msg = msab_pickSecret(&master_password);
558 329 : if (msg)
559 : return msg;
560 : }
561 :
562 341 : if ((opt_pipe = GDKgetenv("sql_optimizer")) && !isOptimizerPipe(opt_pipe))
563 0 : throw(SQL, "sql.init", SQLSTATE(42000) "invalid sql optimizer pipeline %s", opt_pipe);
564 :
565 341 : MT_lock_set(&sql_contextLock);
566 :
567 341 : if (SQLstore) {
568 0 : MT_lock_unset(&sql_contextLock);
569 0 : return MAL_SUCCEED;
570 : }
571 :
572 341 : be_funcs.fcode = &monet5_freecode,
573 341 : be_funcs.fresolve_function = &monet5_resolve_function,
574 341 : be_funcs.fhas_module_function = &monet5_has_module,
575 341 : monet5_user_init(&be_funcs);
576 :
577 341 : if (debug_str)
578 341 : SQLdebug = strtol(debug_str, NULL, 10);
579 341 : if (SQLdebug & 1)
580 0 : GDKtracer_set_component_level("wal", "debug");
581 341 : if (single_user)
582 0 : SQLdebug |= 64;
583 341 : if (readonly)
584 4 : SQLdebug |= 32;
585 :
586 681 : if ((SQLstore = mvc_init(SQLdebug, GDKinmemory(0) ? store_mem : store_bat, readonly, single_user, initpasswd)) == NULL) {
587 1 : MT_lock_unset(&sql_contextLock);
588 1 : throw(SQL, "SQLinit", SQLSTATE(42000) "Catalogue initialization failed");
589 : }
590 340 : sqlinit = GDKgetenv("sqlinit");
591 340 : if (sqlinit) { /* add sqlinit to the fdin stack */
592 0 : buffer *b = (buffer *) GDKmalloc(sizeof(buffer));
593 0 : size_t len = strlen(sqlinit);
594 0 : char* cbuf = _STRDUP(sqlinit);
595 0 : stream *buf;
596 0 : bstream *fdin;
597 :
598 0 : if ( b == NULL || cbuf == NULL) {
599 0 : mvc_exit(SQLstore);
600 0 : SQLstore = NULL;
601 0 : MT_lock_unset(&sql_contextLock);
602 0 : GDKfree(b);
603 0 : GDKfree(cbuf);
604 0 : throw(SQL,"sql.init",SQLSTATE(HY013) MAL_MALLOC_FAIL);
605 : }
606 :
607 0 : buffer_init(b, cbuf, len);
608 0 : buf = buffer_rastream(b, "si");
609 0 : if ( buf == NULL) {
610 0 : mvc_exit(SQLstore);
611 0 : SQLstore = NULL;
612 0 : MT_lock_unset(&sql_contextLock);
613 0 : buffer_destroy(b);
614 0 : throw(SQL,"sql.init",SQLSTATE(HY013) MAL_MALLOC_FAIL);
615 : }
616 :
617 0 : fdin = bstream_create(buf, b->len);
618 0 : if ( fdin == NULL) {
619 0 : mvc_exit(SQLstore);
620 0 : SQLstore = NULL;
621 0 : MT_lock_unset(&sql_contextLock);
622 0 : buffer_destroy(b);
623 0 : throw(SQL,"sql.init",SQLSTATE(HY013) MAL_MALLOC_FAIL);
624 : }
625 :
626 0 : bstream_next(fdin);
627 0 : if ( MCpushClientInput(c, fdin, 0, "") < 0)
628 0 : TRC_ERROR(SQL_PARSER, "Could not switch client input stream\n");
629 : }
630 340 : if ((msg = SQLprepareClient(c, NULL, NULL, NULL)) != NULL) {
631 0 : mvc_exit(SQLstore);
632 0 : SQLstore = NULL;
633 0 : MT_lock_unset(&sql_contextLock);
634 0 : TRC_INFO(SQL_PARSER, "%s\n", msg);
635 0 : return msg;
636 : }
637 340 : be = c->sqlcontext;
638 340 : m = be->mvc;
639 : /* initialize the database with predefined SQL functions */
640 340 : sqlstore *store = SQLstore;
641 340 : if (store->first == 0) {
642 : /* check whether last created object trigger sys.system_update_tables (from 99_system.sql) exists.
643 : * if it doesn't, this is probably a restart of the
644 : * server after an incomplete initialization */
645 119 : if ((msg = SQLtrans(m)) == MAL_SUCCEED) {
646 : /* TODO there's a going issue with loading triggers due to system tables,
647 : so at the moment check for existence of 'json' schema from 40_json.sql */
648 119 : if (!mvc_bind_schema(m, "json"))
649 0 : store->first = 1;
650 119 : msg = mvc_rollback(m, 0, NULL, false);
651 : }
652 119 : if (msg) {
653 0 : freeException(msg);
654 0 : msg = MAL_SUCCEED;
655 : }
656 : }
657 340 : if (store->first > 0) {
658 221 : store->first = 0;
659 221 : maybeupgrade = 0;
660 :
661 221 : qsort(sql_module, sql_modules, sizeof(sql_module[0]), sql_module_compare);
662 8834 : for (int i = 0; i < sql_modules && !msg; i++) {
663 8613 : const char *createdb_inline = (const char*)sql_module[i].code;
664 :
665 8613 : msg = SQLstatementIntern(c, createdb_inline, "sql.init", TRUE, FALSE, NULL);
666 8613 : if (m->sa)
667 0 : sa_destroy(m->sa);
668 8613 : m->sa = NULL;
669 : }
670 : /* 99_system.sql */
671 221 : if (!msg) {
672 221 : const char *createdb_inline =
673 : "create trigger system_update_schemas after update on sys.schemas for each statement call sys_update_schemas();\n"
674 : //"create trigger system_update_tables after update on sys._tables for each statement call sys_update_tables();\n"
675 : /* only system functions until now */
676 : "update sys.functions set system = true;\n"
677 : /* only system tables until now */
678 : "update sys._tables set system = true;\n"
679 : /* only system schemas until now */
680 : "update sys.schemas set system = true;\n"
681 : /* correct invalid FK schema ids, set them to schema id 2000
682 : * (the "sys" schema) */
683 : "update sys.types set schema_id = 2000 where schema_id = 0 and schema_id not in (select id from sys.schemas);\n"
684 : "update sys.functions set schema_id = 2000 where schema_id = 0 and schema_id not in (select id from sys.schemas);\n";
685 221 : msg = SQLstatementIntern(c, createdb_inline, "sql.init", TRUE, FALSE, NULL);
686 221 : if (m->sa)
687 0 : sa_destroy(m->sa);
688 221 : m->sa = NULL;
689 : }
690 : /* Commit after all the startup scripts have been processed */
691 221 : assert(m->session->tr->active);
692 221 : if (mvc_status(m) < 0 || msg)
693 0 : other = mvc_rollback(m, 0, NULL, false);
694 : else
695 221 : other = mvc_commit(m, 0, NULL, false);
696 :
697 221 : if (other && !msg) /* 'msg' variable might be set or not, as well as 'other'. Throw the earliest one */
698 : msg = other;
699 221 : else if (other)
700 0 : freeException(other);
701 221 : if (msg)
702 0 : TRC_INFO(SQL_PARSER, "%s\n", msg);
703 : } else { /* handle upgrades */
704 119 : if (!m->sa)
705 119 : m->sa = sa_create(m->pa);
706 119 : if (!m->sa) {
707 0 : msg = createException(MAL, "createdb", SQLSTATE(HY013) MAL_MALLOC_FAIL);
708 119 : } else if (maybeupgrade) {
709 109 : if ((msg = SQLtrans(m)) == MAL_SUCCEED) {
710 109 : int res = SQLupgrades(c, m);
711 : /* Commit at the end of the upgrade */
712 109 : assert(m->session->tr->active);
713 109 : if (mvc_status(m) < 0 || res)
714 3 : msg = mvc_rollback(m, 0, NULL, false);
715 : else
716 106 : msg = mvc_commit(m, 0, NULL, false);
717 : }
718 : }
719 119 : maybeupgrade = 0;
720 : }
721 340 : fflush(stdout);
722 340 : fflush(stderr);
723 :
724 : /* send error from create scripts back to the first client */
725 340 : if (msg) {
726 0 : msg = handle_error(m, 0, msg);
727 0 : *m->errstr = 0;
728 0 : sqlcleanup(be, mvc_status(m));
729 : }
730 :
731 340 : other = SQLresetClient(c);
732 340 : if (other && !msg) /* 'msg' variable might be set or not, as well as 'other'. Throw the earliest one */
733 : msg = other;
734 340 : else if (other)
735 0 : freeException(other);
736 340 : if (msg != MAL_SUCCEED) {
737 0 : mvc_exit(SQLstore);
738 0 : SQLstore = NULL;
739 0 : MT_lock_unset(&sql_contextLock);
740 0 : return msg;
741 : }
742 :
743 340 : if (GDKinmemory(0)) {
744 1 : MT_lock_unset(&sql_contextLock);
745 1 : return msg;
746 : }
747 :
748 339 : if (MT_create_thread(&sqllogthread, mvc_logmanager, SQLstore, MT_THR_DETACHED, "logmanager") < 0) {
749 0 : mvc_exit(SQLstore);
750 0 : SQLstore = NULL;
751 0 : MT_lock_unset(&sql_contextLock);
752 0 : throw(SQL, "SQLinit", SQLSTATE(42000) "Starting log manager failed");
753 : }
754 :
755 339 : MT_lock_unset(&sql_contextLock);
756 339 : return MAL_SUCCEED;
757 : }
758 :
759 : #define TRANS_ABORTED SQLSTATE(25005) "Current transaction is aborted (please ROLLBACK)\n"
760 :
761 : str
762 18456 : handle_error(mvc *m, int pstatus, str msg)
763 : {
764 18456 : str new = NULL, newmsg = MAL_SUCCEED;
765 :
766 : /* transaction already broken */
767 18456 : if (m->type != Q_TRANS && pstatus < 0) {
768 16236 : freeException(msg);
769 16224 : return createException(SQL,"sql.execute",TRANS_ABORTED);
770 2220 : } else if ( GDKerrbuf && GDKerrbuf[0]){
771 0 : new = GDKstrdup(GDKerrbuf);
772 0 : GDKerrbuf[0] = 0;
773 2239 : } else if ( *m->errstr){
774 7 : new = GDKstrdup(m->errstr);
775 7 : m->errstr[0] = 0;
776 : }
777 2239 : if ( new && msg){
778 0 : newmsg = concatErrors(msg, new);
779 0 : GDKfree(new);
780 2239 : } else if (msg)
781 : newmsg = msg;
782 7 : else if (new) {
783 7 : newmsg = createException(SQL, "sql.execute", "%s", new);
784 7 : GDKfree(new);
785 : } else {
786 0 : newmsg = createException(SQL, "sql.execute", MAL_MALLOC_FAIL);
787 : }
788 : return newmsg;
789 : }
790 :
791 : str
792 259418 : SQLautocommit(mvc *m)
793 : {
794 259418 : str msg = MAL_SUCCEED;
795 :
796 259418 : if (m->session->auto_commit && m->session->tr->active) {
797 171172 : if (mvc_status(m) < 0) {
798 20776 : msg = mvc_rollback(m, 0, NULL, false);
799 : } else {
800 150396 : msg = mvc_commit(m, 0, NULL, false);
801 : }
802 : }
803 259421 : return msg;
804 : }
805 :
806 : str
807 411796 : SQLtrans(mvc *m)
808 : {
809 411796 : if (!m->session->tr->active) {
810 174101 : sql_session *s;
811 :
812 174101 : switch (mvc_trans(m)) {
813 0 : case -1:
814 0 : throw(SQL, "sql.trans", SQLSTATE(HY013) MAL_MALLOC_FAIL);
815 1 : case -3:
816 1 : throw(SQL, "sql.trans", SQLSTATE(42000) "The session's schema was not found, this transaction won't start");
817 : default:
818 174170 : break;
819 : }
820 174170 : s = m->session;
821 174170 : if (!s->schema) {
822 0 : switch (monet5_user_get_def_schema(m, m->user_id, &s->schema_name)) {
823 0 : case -1:
824 0 : mvc_cancel_session(m);
825 0 : throw(SQL, "sql.trans", SQLSTATE(HY013) MAL_MALLOC_FAIL);
826 0 : case -2:
827 0 : mvc_cancel_session(m);
828 0 : throw(SQL, "sql.trans", SQLSTATE(42000) "The user was not found in the database, this session is going to terminate");
829 0 : case -3:
830 0 : mvc_cancel_session(m);
831 0 : throw(SQL, "sql.trans", SQLSTATE(42000) "The user's default schema was not found, this session is going to terminate");
832 : default:
833 0 : break;
834 : }
835 0 : if (!(s->schema = find_sql_schema(s->tr, s->schema_name))) {
836 0 : mvc_cancel_session(m);
837 0 : throw(SQL, "sql.trans", SQLSTATE(42000) "The session's schema was not found, this session is going to terminate");
838 : }
839 : }
840 : }
841 : return MAL_SUCCEED;
842 : }
843 :
844 : str
845 37682 : SQLinitClient(Client c, const char *passwd, const char *challenge, const char *algo)
846 : {
847 37682 : str msg = MAL_SUCCEED;
848 :
849 37682 : MT_lock_set(&sql_contextLock);
850 37690 : if (!SQLstore) {
851 0 : MT_lock_unset(&sql_contextLock);
852 0 : throw(SQL, "SQLinitClient", SQLSTATE(42000) "Catalogue not available");
853 : }
854 37690 : if ((msg = SQLprepareClient(c, passwd, challenge, algo)) == MAL_SUCCEED) {
855 37684 : if (c->usermodule && (c->user != MAL_ADMIN) && (SQLexecPostLoginTriggers(c) != MAL_SUCCEED)) {
856 0 : MT_lock_unset(&sql_contextLock);
857 0 : throw(SQL, "SQLinitClient", SQLSTATE(42000) "Failed to execute post login triggers");
858 : }
859 : }
860 37690 : MT_lock_unset(&sql_contextLock);
861 37690 : return msg;
862 : }
863 :
864 : str
865 181 : SQLinitClientFromMAL(Client c, const char *passwd, const char *challenge, const char *algo)
866 : {
867 181 : str msg = MAL_SUCCEED;
868 :
869 181 : if ((msg = SQLinitClient(c, passwd, challenge, algo)) != MAL_SUCCEED) {
870 0 : c->mode = FINISHCLIENT;
871 0 : return msg;
872 : }
873 :
874 181 : mvc* m = ((backend*) c->sqlcontext)->mvc;
875 181 : if (c->glb)
876 181 : c->glb->keepTmps = true;
877 :
878 : /* Crucial step:
879 : * MAL scripts that interact with the sql module
880 : * must have a properly initialized transaction.
881 : */
882 181 : if ((msg = SQLtrans(m)) != MAL_SUCCEED) {
883 0 : c->mode = FINISHCLIENT;
884 0 : return msg;
885 : }
886 : return msg;
887 : }
888 :
889 : str
890 37674 : SQLexitClient(Client c)
891 : {
892 37674 : str err;
893 :
894 37674 : MT_lock_set(&sql_contextLock);
895 37681 : if (!SQLstore) {
896 0 : MT_lock_unset(&sql_contextLock);
897 0 : throw(SQL, "SQLexitClient", SQLSTATE(42000) "Catalogue not available");
898 : }
899 37681 : err = SQLresetClient(c);
900 37681 : MT_lock_unset(&sql_contextLock);
901 37681 : if (err != MAL_SUCCEED)
902 : return err;
903 37681 : err = MALexitClient(c);
904 37681 : if (err != MAL_SUCCEED)
905 : return err;
906 : return MAL_SUCCEED;
907 : }
908 :
909 : str
910 12 : SQLstatement(Client cntxt, MalBlkPtr mb, MalStkPtr stk, InstrPtr pci)
911 : {
912 12 : const char *expr = *getArgReference_str(stk, pci, 1);
913 :
914 12 : (void) mb;
915 :
916 12 : protocol_version backup = cntxt->protocol;
917 :
918 12 : if (pci->argc == 3 && *getArgReference_bit(stk, pci, 2))
919 1 : cntxt->protocol = PROTOCOL_COLUMNAR;
920 :
921 12 : str msg = SQLstatementIntern(cntxt, expr, "SQLstatement", TRUE, TRUE, NULL);
922 :
923 12 : cntxt->protocol = backup;
924 :
925 12 : return msg;
926 : }
927 :
928 : /*
929 : * Locate a file with SQL commands and execute it. For the time being a 1MB
930 : * file limit is implicitly imposed. If the file can not be located in the
931 : * script library, we assume it is sufficiently self descriptive.
932 : * (Respecting the file system context where the call is executed )
933 : */
934 : str
935 0 : SQLinclude(Client cntxt, MalBlkPtr mb, MalStkPtr stk, InstrPtr pci)
936 : {
937 0 : stream *fd;
938 0 : bstream *bfd;
939 0 : str *name = getArgReference_str(stk, pci, 1);
940 0 : str msg = MAL_SUCCEED, fullname;
941 0 : mvc *m;
942 0 : size_t sz;
943 :
944 0 : fullname = MSP_locate_sqlscript(*name, 0);
945 0 : if (fullname == NULL)
946 0 : fullname = *name;
947 0 : fd = open_rastream(fullname);
948 0 : if (mnstr_errnr(fd) == MNSTR_OPEN_ERROR) {
949 0 : close_stream(fd);
950 0 : throw(MAL, "sql.include", SQLSTATE(42000) "%s\n", mnstr_peek_error(NULL));
951 : }
952 0 : sz = getFileSize(fd);
953 0 : if (sz > (size_t) 1 << 29) {
954 0 : close_stream(fd);
955 0 : throw(MAL, "sql.include", SQLSTATE(42000) "file %s too large to process", fullname);
956 : }
957 0 : if ((bfd = bstream_create(fd, sz == 0 ? (size_t) (128 * BLOCK) : sz)) == NULL) {
958 0 : close_stream(fd);
959 0 : throw(MAL, "sql.include", SQLSTATE(HY013) MAL_MALLOC_FAIL);
960 : }
961 0 : if (bstream_next(bfd) < 0) {
962 0 : bstream_destroy(bfd);
963 0 : throw(MAL, "sql.include", SQLSTATE(42000) "could not read %s\n", *name);
964 : }
965 :
966 0 : msg = SQLstatementIntern(cntxt, bfd->buf, "sql.include", TRUE, FALSE, NULL);
967 0 : bstream_destroy(bfd);
968 0 : m = ((backend *) cntxt->sqlcontext)->mvc;
969 0 : if (m->sa)
970 0 : sa_destroy(m->sa);
971 0 : m->sa = NULL;
972 0 : (void) mb;
973 0 : return msg;
974 : }
975 :
976 : /*
977 : * The SQL reader collects a (sequence) of statements from the input
978 : * stream, but only when no unresolved 'nxt' character is visible.
979 : * In combination with SQLparser this ensures that all statements
980 : * are handled one by one.
981 : *
982 : * The SQLreader is called from two places: the SQL parser and
983 : * the MAL debugger.
984 : * The former only occurs during the parsing phase and the
985 : * second only during exection.
986 : * This means we can safely change the language setting for
987 : * the duration of these calls.
988 : */
989 :
990 : static str
991 443533 : SQLreader(Client c, backend *be)
992 : {
993 443533 : bool go = true;
994 443533 : str msg = MAL_SUCCEED;
995 443533 : bool more = true;
996 443533 : bool commit_done = false;
997 443533 : bstream *in = c->fdin;
998 443533 : int language = -1;
999 443533 : mvc *m = NULL;
1000 443533 : bool blocked = isa_block_stream(in->s);
1001 :
1002 443665 : if (!SQLstore || c->mode <= FINISHCLIENT) {
1003 0 : c->mode = FINISHCLIENT;
1004 0 : return MAL_SUCCEED;
1005 : }
1006 443665 : language = be->language; /* 'S', 's' or 'X' */
1007 443665 : m = be->mvc;
1008 443665 : m->errstr[0] = 0;
1009 : /*
1010 : * Continue processing any left-over input from the previous round.
1011 : */
1012 :
1013 859885 : while (more) {
1014 453578 : more = false;
1015 :
1016 : /* Different kinds of supported statements sequences
1017 : A; -- single line s
1018 : A \n B; -- multi line S
1019 : A; B; -- compound single block s
1020 : A; -- many multi line
1021 : B \n C; -- statements in one block S
1022 : */
1023 : /* auto_commit on end of statement */
1024 453578 : if (m->scanner.mode == LINE_N && !commit_done) {
1025 41514 : msg = SQLautocommit(m);
1026 41510 : if (msg)
1027 : break;
1028 : commit_done = true;
1029 : }
1030 453574 : if (m->session->tr && m->session->tr->active) {
1031 398043 : MT_lock_set(&mal_contextLock);
1032 398407 : c->idle = 0;
1033 398407 : MT_lock_unset(&mal_contextLock);
1034 : }
1035 :
1036 454112 : if (go && in->pos >= in->len) {
1037 268903 : ssize_t rd;
1038 :
1039 268903 : if (c->bak) {
1040 0 : in = c->fdin;
1041 0 : blocked = isa_block_stream(in->s);
1042 0 : m->scanner.rs = c->fdin;
1043 0 : c->fdin->pos += c->yycur;
1044 0 : c->yycur = 0;
1045 : }
1046 268903 : if (in->eof || !blocked) {
1047 259309 : language = 0;
1048 :
1049 : /* The rules of auto_commit require us to finish
1050 : and start a transaction on the start of a new statement (s A;B; case) */
1051 259309 : if (!commit_done) {
1052 217674 : msg = SQLautocommit(m);
1053 217671 : if (msg)
1054 : break;
1055 217284 : commit_done = true;
1056 217284 : MT_lock_set(&mal_contextLock);
1057 217287 : if (c->idle == 0 && (m->session->tr == NULL || !m->session->tr->active)) {
1058 : /* now the session is idle */
1059 168291 : c->idle = time(0);
1060 : }
1061 217287 : MT_lock_unset(&mal_contextLock);
1062 : }
1063 :
1064 258922 : if (go && ((!blocked && mnstr_write(c->fdout, c->prompt, c->promptlength, 1) != 1) || mnstr_flush(c->fdout, MNSTR_FLUSH_DATA))) {
1065 : go = false;
1066 : break;
1067 : }
1068 258653 : in->eof = false;
1069 : }
1070 268247 : while (bstream_getoob(in) > 0)
1071 : ;
1072 268391 : m->scanner.aborted = false;
1073 268391 : if (in->buf == NULL) {
1074 : more = false;
1075 : go = false;
1076 268391 : } else if (go && (rd = bstream_next(in)) <= 0) {
1077 47230 : if (rd == 0 && in->eof && !mnstr_eof(in->s)) {
1078 : /* we hadn't seen the EOF before, so just try again
1079 : (this time with prompt) */
1080 9773 : more = true;
1081 9773 : continue;
1082 : }
1083 : go = false;
1084 : break;
1085 221237 : } else if (go && language == 0) {
1086 221250 : if (in->buf[in->pos] == 's' && !in->eof) {
1087 211668 : while ((rd = bstream_next(in)) > 0)
1088 : ;
1089 : }
1090 221251 : be->language = in->buf[in->pos++];
1091 221251 : if (be->language == 's') {
1092 211584 : be->language = 'S';
1093 211584 : m->scanner.mode = LINE_1;
1094 9667 : } else if (be->language == 'S') {
1095 3762 : m->scanner.mode = LINE_N;
1096 : }
1097 : }
1098 : }
1099 : }
1100 444142 : if ( (c->sessiontimeout && (GDKusec() - c->session) > c->sessiontimeout) || !go || (strncmp(CURRENT(c), "\\q", 2) == 0)) {
1101 37461 : in->pos = in->len; /* skip rest of the input */
1102 37461 : c->mode = FINISHCLIENT;
1103 37461 : return msg;
1104 : }
1105 : return msg;
1106 : }
1107 :
1108 : static str
1109 5905 : SQLchannelcmd(Client c, backend *be)
1110 : {
1111 5905 : assert(be->language == 'X');
1112 :
1113 5905 : bstream *in = c->fdin;
1114 5905 : stream *out = c->fdout;
1115 5905 : mvc *m = be->mvc;
1116 5905 : str msg = MAL_SUCCEED;
1117 5905 : int n = 0, v, off, len, ok;
1118 :
1119 5905 : if (strncmp(in->buf + in->pos, "export ", 7) == 0)
1120 69 : n = sscanf(in->buf + in->pos + 7, "%d %d %d", &v, &off, &len);
1121 :
1122 5905 : if (n == 2 || n == 3) {
1123 69 : if (n == 2)
1124 19 : len = m->reply_size;
1125 69 : in->pos = in->len; /* HACK: should use parsed length */
1126 69 : if ((ok = mvc_export_chunk(be, out, v, off, len < 0 ? BUN_NONE : (BUN) len)) < 0) {
1127 0 : sqlcleanup(be, 0);
1128 0 : return createException(SQL, "SQLparser", SQLSTATE(45000) "Result set construction failed: %s", mvc_export_error(be, out, ok));
1129 : }
1130 : return MAL_SUCCEED;
1131 : }
1132 5836 : if (strncmp(in->buf + in->pos, "exportbin ", 10) == 0) {
1133 600 : n = sscanf(in->buf + in->pos + 10, "%d %d %d", &v, &off, &len);
1134 600 : if (n == 3) {
1135 600 : if ((ok = mvc_export_bin_chunk(be, out, v, off, len < 0 ? BUN_NONE: (BUN) len)) < 0) {
1136 0 : msg = createException(SQL, "SQLparser", SQLSTATE(45000) "Result set construction failed: %s", mvc_export_error(be, out, ok));
1137 0 : in->pos = in->len; /* HACK: should use parsed length */
1138 0 : sqlcleanup(be, 0);
1139 0 : return msg;
1140 : }
1141 600 : in->pos = in->len; /* HACK: should use parsed length */
1142 600 : return MAL_SUCCEED;
1143 : }
1144 : }
1145 5236 : if (strncmp(in->buf + in->pos, "close ", 6) == 0) {
1146 4387 : res_table *t;
1147 :
1148 4387 : v = (int) strtol(in->buf + in->pos + 6, NULL, 0);
1149 4387 : t = res_tables_find(be->results, v);
1150 4387 : if (t)
1151 484 : be->results = res_tables_remove(be->results, t);
1152 4387 : in->pos = in->len; /* HACK: should use parsed length */
1153 4387 : return MAL_SUCCEED;
1154 : }
1155 849 : if (strncmp(in->buf + in->pos, "release ", 8) == 0) {
1156 211 : cq *q = NULL;
1157 :
1158 211 : v = (int) strtol(in->buf + in->pos + 8, NULL, 0);
1159 211 : if ((q = qc_find(m->qc, v)) != NULL)
1160 204 : qc_delete(m->qc, q);
1161 211 : in->pos = in->len; /* HACK: should use parsed length */
1162 211 : return MAL_SUCCEED;
1163 : }
1164 638 : if (strncmp(in->buf + in->pos, "auto_commit ", 12) == 0) {
1165 173 : int commit;
1166 173 : v = (int) strtol(in->buf + in->pos + 12, NULL, 10);
1167 173 : commit = (!m->session->auto_commit && v);
1168 173 : m->session->auto_commit = (v) != 0;
1169 173 : m->session->ac_on_commit = m->session->auto_commit;
1170 173 : if (m->session->tr->active) {
1171 0 : if (commit) {
1172 0 : msg = mvc_commit(m, 0, NULL, true);
1173 : } else {
1174 0 : msg = mvc_rollback(m, 0, NULL, true);
1175 : }
1176 : }
1177 173 : in->pos = in->len; /* HACK: should use parsed length */
1178 173 : if (msg != NULL)
1179 0 : sqlcleanup(be, 0);
1180 173 : return msg;
1181 : }
1182 465 : static const char* columnar_protocol = "columnar_protocol ";
1183 465 : if (strncmp(in->buf + in->pos, columnar_protocol, strlen(columnar_protocol)) == 0) {
1184 0 : v = (int) strtol(in->buf + in->pos + strlen(columnar_protocol), NULL, 10);
1185 :
1186 0 : c->protocol = v?PROTOCOL_COLUMNAR:PROTOCOL_9;
1187 :
1188 0 : in->pos = in->len; /* HACK: should use parsed length */
1189 0 : return MAL_SUCCEED;
1190 : }
1191 465 : if (strncmp(in->buf + in->pos, "reply_size ", 11) == 0) {
1192 465 : v = (int) strtol(in->buf + in->pos + 11, NULL, 10);
1193 465 : if (v < -1) {
1194 0 : sqlcleanup(be, 0);
1195 0 : return createException(SQL, "SQLparser", SQLSTATE(42000) "Reply_size cannot be negative");
1196 : }
1197 465 : m->reply_size = v;
1198 465 : in->pos = in->len; /* HACK: should use parsed length */
1199 465 : return MAL_SUCCEED;
1200 : }
1201 0 : if (strncmp(in->buf + in->pos, "sizeheader", 10) == 0) { // no underscore
1202 0 : v = (int) strtol(in->buf + in->pos + 10, NULL, 10);
1203 0 : be->sizeheader = v != 0;
1204 0 : in->pos = in->len; /* HACK: should use parsed length */
1205 0 : return MAL_SUCCEED;
1206 : }
1207 0 : if (strncmp(in->buf + in->pos, "quit", 4) == 0) {
1208 0 : c->mode = FINISHCLIENT;
1209 0 : in->pos = in->len; /* HACK: should use parsed length */
1210 0 : return MAL_SUCCEED;
1211 : }
1212 0 : in->pos = in->len; /* HACK: should use parsed length */
1213 0 : msg = createException(SQL, "SQLparser", SQLSTATE(42000) "Unrecognized X command: %s\n", in->buf + in->pos);
1214 0 : sqlcleanup(be, 0);
1215 0 : return msg;
1216 : }
1217 :
1218 : /*
1219 : * The SQL block is stored in the client input buffer, from which it
1220 : * can be parsed by the SQL parser. The client structure contains
1221 : * a small table of bounded tables. This should be reset before we
1222 : * parse a new statement sequence.
1223 : * Before we parse the sql statement, we look for any variable settings
1224 : * for specific commands.
1225 : * The most important one is to prepare code to be handled by the debugger.
1226 : * The current analysis is simple and fulfills our short-term needs.
1227 : * A future version may analyze the parameter settings in more detail.
1228 : */
1229 :
1230 : #define MAX_QUERY (64*1024*1024)
1231 :
1232 : static str
1233 400267 : SQLparser_body(Client c, backend *be)
1234 : {
1235 400267 : str msg = MAL_SUCCEED;
1236 400267 : mvc *m = be->mvc;
1237 400267 : lng Tbegin = 0, Tend = 0;
1238 :
1239 400267 : int pstatus = m->session->status;
1240 :
1241 400267 : int err = 0;
1242 400267 : m->type = Q_PARSE;
1243 400267 : m->emode = m_normal;
1244 400267 : m->emod = mod_none;
1245 400267 : c->query = NULL;
1246 400267 : c->qryctx.starttime = Tbegin = Tend = GDKusec();
1247 400288 : c->qryctx.endtime = c->querytimeout ? c->qryctx.starttime + c->querytimeout : 0;
1248 :
1249 400288 : if ((err = sqlparse(m)) ||
1250 398960 : m->scanner.aborted ||
1251 797757 : ((m->scanner.aborted |= bstream_getoob(m->scanner.rs) != 0) != false) ||
1252 : /* Only forget old errors on transaction boundaries */
1253 798213 : (mvc_status(m) && m->type != Q_TRANS) || !m->sym) {
1254 149013 : if (!err && m->scanner.started) /* repeat old errors, with a parsed query */
1255 16988 : err = mvc_status(m);
1256 149225 : if (m->scanner.aborted) {
1257 0 : msg = createException(PARSE, "SQLparser", "Query aborted");
1258 0 : *m->errstr = 0;
1259 149225 : } else if (err && *m->errstr) {
1260 1085 : if (strlen(m->errstr) > 6 && m->errstr[5] == '!')
1261 1084 : msg = createException(PARSE, "SQLparser", "%s", m->errstr);
1262 : else
1263 1 : msg = createException(PARSE, "SQLparser", SQLSTATE(42000) "%s", m->errstr);
1264 1085 : *m->errstr = 0;
1265 : }
1266 149225 : if (m->sym)
1267 16289 : msg = handle_error(m, pstatus, msg);
1268 149260 : if (!m->sym) /* skip empty input */
1269 132957 : m->emode = m_deallocate;
1270 149260 : sqlcleanup(be, err);
1271 149418 : goto finalize;
1272 : }
1273 : /*
1274 : * We have dealt with the first parsing step and advanced the input reader
1275 : * to the next statement (if any).
1276 : * Now is the time to also perform the semantic analysis, optimize and
1277 : * produce code.
1278 : */
1279 250054 : c->query = query_cleaned(m->sa, QUERY(m->scanner));
1280 :
1281 250168 : if (profilerStatus > 0) {
1282 0 : profilerEvent(NULL,
1283 : &(struct NonMalEvent)
1284 0 : {TEXT_TO_SQL, c, Tend, &m->session->tr->ts, NULL, c->query?0:1, Tend-Tbegin});
1285 : }
1286 :
1287 250168 : if (c->query == NULL) {
1288 0 : err = 1;
1289 0 : msg = createException(PARSE, "SQLparser", SQLSTATE(HY013) MAL_MALLOC_FAIL);
1290 250168 : } else if (m->emode == m_deallocate) {
1291 8 : AtomNode *an = (AtomNode *) m->sym;
1292 8 : assert(m->sym->type == type_symbol && an->a->data.vtype == TYPE_int);
1293 8 : int preparedid = an->a->data.val.ival;
1294 :
1295 8 : if (preparedid > -1) { /* The -1 case represents the deallocate the entire query cache */
1296 4 : be->q = qc_find(m->qc, preparedid);
1297 4 : if (!be->q) {
1298 2 : msg = createException(SQL, "DEALLOC", SQLSTATE(07003) "No prepared statement with id: %d\n", preparedid);
1299 2 : *m->errstr = 0;
1300 2 : msg = handle_error(m, pstatus, msg);
1301 2 : sqlcleanup(be, -1);
1302 2 : return msg;
1303 : } else {
1304 2 : qc_delete(m->qc, be->q);
1305 2 : be->q = NULL;
1306 : }
1307 : } else {
1308 4 : qc_clean(m->qc);
1309 : }
1310 :
1311 6 : m->type = Q_SCHEMA; /* TODO DEALLOCATE statements don't fit for Q_SCHEMA */
1312 6 : scanner_query_processed(&(m->scanner));
1313 :
1314 : /* For deallocate statements just export a simple output */
1315 6 : if (!GDKembedded() && (err = mvc_export_operation(be, c->fdout, "", c->qryctx.starttime, c->curprg->def->optimize)) < 0)
1316 0 : msg = createException(PARSE, "SQLparser", SQLSTATE(45000) "Export operation failed: %s", mvc_export_error(be, c->fdout, err));
1317 6 : sqlcleanup(be, 0);
1318 6 : return msg;
1319 : } else {
1320 250160 : sql_rel *r = sql_symbol2relation(be, m->sym);
1321 :
1322 249620 : if (!r || (err = mvc_status(m) && m->type != Q_TRANS && *m->errstr)) {
1323 2224 : if (strlen(m->errstr) > 6 && m->errstr[5] == '!')
1324 2223 : msg = createException(PARSE, "SQLparser", "%s", m->errstr);
1325 : else
1326 1 : msg = createException(PARSE, "SQLparser", SQLSTATE(42000) "%s", m->errstr);
1327 2224 : *m->errstr = 0;
1328 2224 : msg = handle_error(m, pstatus, msg);
1329 2224 : sqlcleanup(be, err);
1330 2224 : goto finalize;
1331 : }
1332 :
1333 247211 : int oldvtop = c->curprg->def->vtop;
1334 247211 : int oldstop = c->curprg->def->stop;
1335 247211 : be->vtop = oldvtop;
1336 247211 : (void)runtimeProfileSetTag(c); /* generate and set the tag in the mal block of the clients current program. */
1337 248300 : if (m->emode != m_prepare || (m->emode == m_prepare && (m->emod & mod_exec) && is_ddl(r->op)) /* direct execution prepare */) {
1338 247965 : scanner_query_processed(&(m->scanner));
1339 :
1340 247961 : err = 0;
1341 247961 : setVarType(c->curprg->def, 0, 0);
1342 247961 : if (m->emode != m_prepare && be->subbackend && be->subbackend->check(be->subbackend, r)) {
1343 0 : res_table *rt = NULL;
1344 0 : if (be->subbackend->exec(be->subbackend, r, be->result_id++, &rt) == NULL) { /* on error fall back */
1345 0 : be->subbackend->reset(be->subbackend);
1346 0 : if (rt) {
1347 0 : rt->next = be->results;
1348 0 : be->results = rt;
1349 : }
1350 0 : return NULL;
1351 : }
1352 0 : be->subbackend->reset(be->subbackend);
1353 : }
1354 :
1355 247961 : Tbegin = GDKusec();
1356 :
1357 247934 : int opt = 0;
1358 247934 : if (m->emode == m_prepare && (m->emod & mod_exec)) {
1359 : /* generated the named parameters for the placeholders */
1360 3 : if (backend_dumpstmt(be, c->curprg->def, r->r, !(m->emod & mod_exec), 0, c->query) < 0) {
1361 0 : msg = handle_error(m, 0, msg);
1362 0 : err = 1;
1363 0 : MSresetInstructions(c->curprg->def, oldstop);
1364 0 : freeVariables(c, c->curprg->def, NULL, oldvtop);
1365 : }
1366 3 : r = r->l;
1367 3 : m->emode = m_normal;
1368 3 : m->emod &= ~mod_exec;
1369 : }
1370 247934 : if (!err && backend_dumpstmt(be, c->curprg->def, r, !(m->emod & mod_exec), 0, c->query) < 0) {
1371 6 : msg = handle_error(m, 0, msg);
1372 6 : err = 1;
1373 6 : MSresetInstructions(c->curprg->def, oldstop);
1374 6 : freeVariables(c, c->curprg->def, NULL, oldvtop);
1375 6 : freeException(c->curprg->def->errors);
1376 6 : c->curprg->def->errors = NULL;
1377 : } else
1378 247799 : opt = ((m->emod & mod_exec) == 0); /* no need to optimze prepare - execute */
1379 :
1380 247805 : Tend = GDKusec();
1381 247582 : if(profilerStatus > 0)
1382 0 : profilerEvent(NULL,
1383 : &(struct NonMalEvent)
1384 0 : {REL_TO_MAL, c, Tend, NULL, NULL, c->query?0:1, Tend-Tbegin});
1385 247582 : if (err)
1386 6 : m->session->status = -10;
1387 6 : if (err == 0) {
1388 : /* no parsing error encountered, finalize the code of the query wrapper */
1389 247576 : pushEndInstruction(c->curprg->def);
1390 :
1391 : /* check the query wrapper for errors */
1392 247848 : if (msg == MAL_SUCCEED)
1393 247701 : msg = chkTypes(c->usermodule, c->curprg->def, TRUE);
1394 :
1395 247565 : if (msg == MAL_SUCCEED && opt) {
1396 241668 : Tbegin = Tend;
1397 241668 : msg = SQLoptimizeQuery(c, c->curprg->def);
1398 241674 : Tend = GDKusec();
1399 241790 : if (profilerStatus > 0)
1400 0 : profilerEvent(NULL,
1401 : &(struct NonMalEvent)
1402 0 : {MAL_OPT, c, Tend, NULL, NULL, msg==MAL_SUCCEED?0:1, Tend-Tbegin});
1403 241790 : if (msg != MAL_SUCCEED) {
1404 13 : str other = c->curprg->def->errors;
1405 13 : c->curprg->def->errors = 0;
1406 13 : MSresetInstructions(c->curprg->def, oldstop);
1407 13 : freeVariables(c, c->curprg->def, NULL, oldvtop);
1408 13 : if (other != msg)
1409 13 : freeException(other);
1410 13 : goto finalize;
1411 : }
1412 : }
1413 :
1414 : /* we know more in this case than chkProgram(c->fdout, c->usermodule, c->curprg->def); */
1415 247674 : if (msg == MAL_SUCCEED && c->curprg->def->errors) {
1416 0 : msg = c->curprg->def->errors;
1417 0 : c->curprg->def->errors = 0;
1418 : /* restore the state */
1419 0 : MSresetInstructions(c->curprg->def, oldstop);
1420 0 : freeVariables(c, c->curprg->def, NULL, oldvtop);
1421 0 : if (msg == NULL && *m->errstr){
1422 : if (strlen(m->errstr) > 6 && m->errstr[5] == '!')
1423 : msg = createException(PARSE, "SQLparser", "%s", m->errstr);
1424 : else
1425 : msg = createException(PARSE, "SQLparser", SQLSTATE(M0M27) "Semantic errors %s", m->errstr);
1426 : *m->errstr = 0;
1427 : } else if (msg) {
1428 0 : str newmsg = createException(PARSE, "SQLparser", SQLSTATE(M0M27) "Semantic errors %s", msg);
1429 0 : freeException(msg);
1430 0 : msg = newmsg;
1431 : }
1432 : }
1433 : }
1434 : } else {
1435 335 : char *q_copy = sa_strdup(m->sa, c->query);
1436 :
1437 335 : be->q = NULL;
1438 335 : if (!q_copy) {
1439 0 : msg = createException(PARSE, "SQLparser", SQLSTATE(HY013) MAL_MALLOC_FAIL);
1440 0 : err = 1;
1441 : } else {
1442 670 : be->q = qc_insert(m->qc, m->sa, /* the allocator */
1443 : r, /* keep relational query */
1444 335 : m->sym, /* the sql symbol tree */
1445 : m->params, /* the argument list */
1446 : m->type, /* the type of the statement */
1447 : q_copy,
1448 335 : be->no_mitosis);
1449 335 : if (!be->q) {
1450 0 : msg = createException(PARSE, "SQLparser", SQLSTATE(HY013) MAL_MALLOC_FAIL);
1451 0 : err = 1;
1452 : }
1453 : }
1454 335 : scanner_query_processed(&(m->scanner));
1455 335 : if (be->q && backend_dumpproc(be, c, be->q, r) < 0) {
1456 1 : msg = handle_error(m, 0, msg);
1457 1 : err = 1;
1458 : }
1459 :
1460 : /* passed over to query cache, used during dumpproc */
1461 335 : m->sa = NULL;
1462 335 : m->sym = NULL;
1463 335 : m->runs = NULL;
1464 335 : m->params = NULL;
1465 :
1466 335 : if (be->q) {
1467 335 : int res = 0;
1468 335 : if (!err && (res = mvc_export_prepare(be, c->fdout)) < 0) {
1469 0 : msg = createException(PARSE, "SQLparser", SQLSTATE(45000) "Export operation failed: %s", mvc_export_error(be, c->fdout, res));
1470 0 : err = 1;
1471 : }
1472 0 : if (err) {
1473 1 : be->q->name = NULL; /* later remove cleanup from mal from qc code */
1474 1 : qc_delete(m->qc, be->q);
1475 : }
1476 335 : be->result_id = be->q->id;
1477 335 : be->q = NULL;
1478 : }
1479 335 : if (err)
1480 1 : m->session->status = -10;
1481 335 : sqlcleanup(be, 0);
1482 335 : c->query = NULL;
1483 335 : return msg;
1484 : }
1485 : }
1486 399335 : finalize:
1487 399335 : if (m->sa)
1488 399335 : eb_init(&m->sa->eb); /* exiting the scope where the exception buffer can be used */
1489 398789 : if (msg) {
1490 19650 : sqlcleanup(be, 0);
1491 19652 : c->query = NULL;
1492 : }
1493 : return msg;
1494 : }
1495 :
1496 : static str
1497 400329 : SQLparser(Client c, backend *be)
1498 : {
1499 400329 : mvc *m = be->mvc;
1500 400329 : char *msg;
1501 :
1502 400329 : assert (be->language != 'X');
1503 :
1504 400329 : if ((msg = SQLtrans(m)) != MAL_SUCCEED) {
1505 1 : c->mode = FINISHCLIENT;
1506 1 : return msg;
1507 : }
1508 :
1509 : /* sqlparse needs sql allocator to be available. It can be NULL at
1510 : * this point if this is a recursive call. */
1511 400356 : if (m->sa == NULL)
1512 37725 : m->sa = sa_create(m->pa);
1513 400354 : if (m->sa == NULL) {
1514 0 : c->mode = FINISHCLIENT;
1515 0 : throw(SQL, "SQLparser", SQLSTATE(HY013) MAL_MALLOC_FAIL " for SQL allocator");
1516 : }
1517 400285 : if (eb_savepoint(&m->sa->eb)) {
1518 0 : msg = createException(SQL, "SQLparser", "%s", m->sa->eb.msg);
1519 0 : eb_init(&m->sa->eb);
1520 0 : sa_reset(m->sa);
1521 0 : if (c && c->curprg && c->curprg->def && c->curprg->def->errors) {
1522 0 : freeException(c->curprg->def->errors);
1523 0 : c->curprg->def->errors = NULL;
1524 : }
1525 0 : sqlcleanup(be, 0);
1526 0 : c->query = NULL;
1527 0 : return msg;
1528 : }
1529 400285 : return SQLparser_body(c, be);
1530 : }
1531 :
1532 : str
1533 443537 : SQLengine_(Client c)
1534 : {
1535 443537 : backend *be = (backend *) c->sqlcontext;
1536 :
1537 443537 : if (be == 0) {
1538 : /* leave a message in the log */
1539 0 : TRC_ERROR(SQL_PARSER, "SQL state description is missing, cannot handle client!\n");
1540 : /* stop here, instead of printing the exception below to the
1541 : * client in an endless loop */
1542 0 : c->mode = FINISHCLIENT;
1543 0 : throw(SQL, "SQLparser", SQLSTATE(42000) "State descriptor missing, aborting");
1544 : }
1545 :
1546 443537 : str msg = SQLreader(c, be);
1547 444111 : if (msg || c->mode <= FINISHCLIENT)
1548 : return msg;
1549 :
1550 406302 : if (be->language == 'X') {
1551 5905 : return SQLchannelcmd(c, be);
1552 400397 : } else if (be->language !='S') {
1553 0 : msg = createException(SQL, "SQLparser", SQLSTATE(42000) "Unrecognized language prefix: %ci\n", be->language);
1554 0 : c->mode = FINISHCLIENT; /* and disconnect, as client doesn't respect the mapi protocol */
1555 0 : sqlcleanup(be, 0);
1556 0 : c->query = NULL;
1557 : } else {
1558 400397 : msg = SQLparser(c, be);
1559 399036 : if (msg == MAL_SUCCEED && (be->mvc->emode == m_deallocate || be->mvc->emode == m_prepare))
1560 : return msg;
1561 : }
1562 247763 : if (msg || c->mode <= FINISHCLIENT)
1563 18998 : return msg;
1564 :
1565 247763 : if (c->curprg->def->stop == 1) {
1566 0 : sqlcleanup(be, 0);
1567 0 : return NULL;
1568 : }
1569 247763 : return SQLengineIntern(c, be);
1570 : }
1571 :
1572 : void
1573 443659 : SQLengine(Client c)
1574 : {
1575 443659 : char *msg = SQLengine_(c);
1576 443381 : if (msg) {
1577 : /* remove exception decoration */
1578 76026 : for (char *m = msg; m && *m; ) {
1579 38002 : char *n = strchr(m, '\n');
1580 38002 : char *s = getExceptionMessageAndState(m);
1581 38217 : mnstr_printf(c->fdout, "!%.*s\n", (int) (n - s), s);
1582 38210 : m = n;
1583 38210 : if (n) {
1584 38210 : m++; /* include newline */
1585 : }
1586 : }
1587 38024 : freeException(msg);
1588 : }
1589 443606 : }
1590 :
1591 : str
1592 0 : SYSupdate_tables(Client cntxt, MalBlkPtr mb, MalStkPtr stk, InstrPtr pci)
1593 : {
1594 0 : mvc *m = ((backend *) cntxt->sqlcontext)->mvc;
1595 :
1596 0 : (void) mb;
1597 0 : (void) stk;
1598 0 : (void) pci;
1599 :
1600 0 : sql_trans_update_tables(m->session->tr, mvc_bind_schema(m, "sys"));
1601 0 : return MAL_SUCCEED;
1602 : }
1603 :
1604 : str
1605 221 : SYSupdate_schemas(Client cntxt, MalBlkPtr mb, MalStkPtr stk, InstrPtr pci)
1606 : {
1607 221 : mvc *m = ((backend *) cntxt->sqlcontext)->mvc;
1608 :
1609 221 : (void) mb;
1610 221 : (void) stk;
1611 221 : (void) pci;
1612 :
1613 221 : if (sql_trans_update_schemas(m->session->tr) < 0)
1614 0 : throw(MAL, "sql.update_schemas", MAL_MALLOC_FAIL);
1615 : return MAL_SUCCEED;
1616 : }
|