| 1 | /* |
| 2 | * This Source Code Form is subject to the terms of the Mozilla Public |
| 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
| 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. |
| 5 | * |
| 6 | * Copyright 1997 - July 2008 CWI, August 2008 - 2019 MonetDB B.V. |
| 7 | */ |
| 8 | |
| 9 | /* |
| 10 | * SQL execution |
| 11 | * N. Nes, M.L. Kersten |
| 12 | */ |
| 13 | /* |
| 14 | * Execution of SQL instructions. |
| 15 | * Before we are can process SQL statements the global catalog should be initialized. |
| 16 | */ |
| 17 | #include "monetdb_config.h" |
| 18 | #include "mal_backend.h" |
| 19 | #include "sql_scenario.h" |
| 20 | #include "sql_result.h" |
| 21 | #include "sql_gencode.h" |
| 22 | #include "sql_assert.h" |
| 23 | #include "sql_execute.h" |
| 24 | #include "sql_env.h" |
| 25 | #include "sql_mvc.h" |
| 26 | #include "sql_user.h" |
| 27 | #include "sql_optimizer.h" |
| 28 | #include "sql_datetime.h" |
| 29 | #include "rel_unnest.h" |
| 30 | #include "rel_optimizer.h" |
| 31 | #include "rel_partition.h" |
| 32 | #include "rel_distribute.h" |
| 33 | #include "rel_select.h" |
| 34 | #include "rel_rel.h" |
| 35 | #include "rel_exp.h" |
| 36 | #include "rel_dump.h" |
| 37 | #include "mal_debugger.h" |
| 38 | #include "mtime.h" |
| 39 | #include "optimizer.h" |
| 40 | #include "opt_inline.h" |
| 41 | #include <unistd.h> |
| 42 | |
| 43 | /* |
| 44 | * The SQLcompile operation can be used by separate |
| 45 | * front-ends to benefit from the SQL functionality. |
| 46 | * It expects a string and returns the name of the |
| 47 | * corresponding MAL block as it is known in the |
| 48 | * SQL_cache, where it can be picked up. |
| 49 | * The SQLstatement operation also executes the instruction upon request. |
| 50 | * |
| 51 | * In both cases the SQL string is handled like an ordinary |
| 52 | * user query, following the same optimization paths and |
| 53 | * caching. |
| 54 | */ |
| 55 | |
| 56 | /* #define _SQL_COMPILE */ |
| 57 | |
| 58 | /* |
| 59 | * BEWARE: SQLstatementIntern only commits after all statements found |
| 60 | * in expr are executed, when autocommit mode is enabled. |
| 61 | * |
| 62 | * The tricky part for this statement is to ensure that the SQL statement |
| 63 | * is executed within the client context specified. This leads to context juggling. |
| 64 | */ |
| 65 | |
| 66 | /* |
| 67 | * The trace operation collects the events in the BATs |
| 68 | * and creates a secondary result set upon termination |
| 69 | * of the query. |
| 70 | * |
| 71 | * SQLsetTrace extends the MAL plan with code to collect the events. |
| 72 | * from the profile cache and returns it as a secondary resultset. |
| 73 | */ |
| 74 | static str |
| 75 | SQLsetTrace(Client cntxt, MalBlkPtr mb) |
| 76 | { |
| 77 | InstrPtr q, resultset; |
| 78 | InstrPtr tbls, cols, types, clen, scale; |
| 79 | str msg = MAL_SUCCEED; |
| 80 | int k; |
| 81 | |
| 82 | if((msg = startTrace(cntxt)) != MAL_SUCCEED) |
| 83 | return msg; |
| 84 | clearTrace(cntxt); |
| 85 | |
| 86 | for(k= mb->stop-1; k>0; k--) |
| 87 | if( getInstrPtr(mb,k)->token ==ENDsymbol) |
| 88 | break; |
| 89 | mb->stop=k; |
| 90 | |
| 91 | q= newStmt(mb, profilerRef, stoptraceRef); |
| 92 | |
| 93 | /* cook a new resultSet instruction */ |
| 94 | resultset = newInstruction(mb,sqlRef, resultSetRef); |
| 95 | getArg(resultset,0) = newTmpVariable(mb, TYPE_int); |
| 96 | |
| 97 | /* build table defs */ |
| 98 | tbls = newStmt(mb,batRef, newRef); |
| 99 | setVarType(mb, getArg(tbls,0), newBatType(TYPE_str)); |
| 100 | tbls = pushType(mb, tbls, TYPE_str); |
| 101 | |
| 102 | q= newStmt(mb,batRef,appendRef); |
| 103 | q= pushArgument(mb,q,getArg(tbls,0)); |
| 104 | q= pushStr(mb,q,".trace" ); |
| 105 | k= getArg(q,0); |
| 106 | |
| 107 | q= newStmt(mb,batRef,appendRef); |
| 108 | q= pushArgument(mb,q,k); |
| 109 | q= pushStr(mb,q,".trace" ); |
| 110 | |
| 111 | resultset= pushArgument(mb,resultset, getArg(q,0)); |
| 112 | |
| 113 | /* build colum defs */ |
| 114 | cols = newStmt(mb,batRef, newRef); |
| 115 | setVarType(mb, getArg(cols,0), newBatType(TYPE_str)); |
| 116 | cols = pushType(mb, cols, TYPE_str); |
| 117 | |
| 118 | q= newStmt(mb,batRef,appendRef); |
| 119 | q= pushArgument(mb,q,getArg(cols,0)); |
| 120 | q= pushStr(mb,q,"usec" ); |
| 121 | k= getArg(q,0); |
| 122 | |
| 123 | q= newStmt(mb,batRef,appendRef); |
| 124 | q= pushArgument(mb,q, k); |
| 125 | q= pushStr(mb,q,"statement" ); |
| 126 | |
| 127 | resultset= pushArgument(mb,resultset, getArg(q,0)); |
| 128 | |
| 129 | /* build type defs */ |
| 130 | types = newStmt(mb,batRef, newRef); |
| 131 | setVarType(mb, getArg(types,0), newBatType(TYPE_str)); |
| 132 | types = pushType(mb, types, TYPE_str); |
| 133 | |
| 134 | q= newStmt(mb,batRef,appendRef); |
| 135 | q= pushArgument(mb,q, getArg(types,0)); |
| 136 | q= pushStr(mb,q,"bigint" ); |
| 137 | k= getArg(q,0); |
| 138 | |
| 139 | q= newStmt(mb,batRef,appendRef); |
| 140 | q= pushArgument(mb,q, k); |
| 141 | q= pushStr(mb,q,"clob" ); |
| 142 | |
| 143 | resultset= pushArgument(mb,resultset, getArg(q,0)); |
| 144 | |
| 145 | /* build scale defs */ |
| 146 | clen = newStmt(mb,batRef, newRef); |
| 147 | setVarType(mb, getArg(clen,0), newBatType(TYPE_int)); |
| 148 | clen = pushType(mb, clen, TYPE_int); |
| 149 | |
| 150 | q= newStmt(mb,batRef,appendRef); |
| 151 | q= pushArgument(mb,q, getArg(clen,0)); |
| 152 | q= pushInt(mb,q,64); |
| 153 | k= getArg(q,0); |
| 154 | |
| 155 | q= newStmt(mb,batRef,appendRef); |
| 156 | q= pushArgument(mb,q, k); |
| 157 | q= pushInt(mb,q,0); |
| 158 | |
| 159 | resultset= pushArgument(mb,resultset, getArg(q,0)); |
| 160 | |
| 161 | /* build scale defs */ |
| 162 | scale = newStmt(mb,batRef, newRef); |
| 163 | setVarType(mb, getArg(scale,0), newBatType(TYPE_int)); |
| 164 | scale = pushType(mb, scale, TYPE_int); |
| 165 | |
| 166 | q= newStmt(mb,batRef,appendRef); |
| 167 | q= pushArgument(mb,q, getArg(scale,0)); |
| 168 | q= pushInt(mb,q,0); |
| 169 | k= getArg(q,0); |
| 170 | |
| 171 | q= newStmt(mb,batRef,appendRef); |
| 172 | q= pushArgument(mb, q, k); |
| 173 | q= pushInt(mb,q,0); |
| 174 | |
| 175 | resultset= pushArgument(mb,resultset, getArg(q,0)); |
| 176 | |
| 177 | /* add the ticks column */ |
| 178 | |
| 179 | q = newStmt(mb, profilerRef, "getTrace" ); |
| 180 | q = pushStr(mb, q, putName("usec" )); |
| 181 | resultset= pushArgument(mb,resultset, getArg(q,0)); |
| 182 | |
| 183 | /* add the stmt column */ |
| 184 | q = newStmt(mb, profilerRef, "getTrace" ); |
| 185 | q = pushStr(mb, q, putName("stmt" )); |
| 186 | resultset= pushArgument(mb,resultset, getArg(q,0)); |
| 187 | |
| 188 | pushInstruction(mb,resultset); |
| 189 | pushEndInstruction(mb); |
| 190 | chkTypes(cntxt->usermodule, mb, TRUE); |
| 191 | |
| 192 | return msg; |
| 193 | } |
| 194 | |
| 195 | /* |
| 196 | * Execution of the SQL program is delegated to the MALengine. |
| 197 | * Different cases should be distinguished. The default is to |
| 198 | * hand over the MAL block derived by the parser for execution. |
| 199 | * However, when we received an Execute call, we make a shortcut |
| 200 | * and prepare the stack for immediate execution |
| 201 | */ |
| 202 | static str |
| 203 | SQLexecutePrepared(Client c, backend *be, MalBlkPtr mb) |
| 204 | { |
| 205 | mvc *m = be->mvc; |
| 206 | int argc, parc; |
| 207 | ValPtr *argv, argvbuffer[MAXARG], v; |
| 208 | ValRecord *argrec, argrecbuffer[MAXARG]; |
| 209 | MalStkPtr glb; |
| 210 | InstrPtr pci; |
| 211 | int i; |
| 212 | str ret; |
| 213 | cq *q= be->q; |
| 214 | |
| 215 | pci = getInstrPtr(mb, 0); |
| 216 | if (pci->argc >= MAXARG){ |
| 217 | argv = (ValPtr *) GDKmalloc(sizeof(ValPtr) * pci->argc); |
| 218 | if( argv == NULL) |
| 219 | throw(SQL,"sql.prepare" ,SQLSTATE(HY001) MAL_MALLOC_FAIL); |
| 220 | } else |
| 221 | argv = argvbuffer; |
| 222 | |
| 223 | if (pci->retc >= MAXARG){ |
| 224 | argrec = (ValRecord *) GDKmalloc(sizeof(ValRecord) * pci->retc); |
| 225 | if( argrec == NULL){ |
| 226 | if( argv != argvbuffer) |
| 227 | GDKfree(argv); |
| 228 | throw(SQL,"sql.prepare" ,SQLSTATE(HY001) MAL_MALLOC_FAIL); |
| 229 | } |
| 230 | } else |
| 231 | argrec = argrecbuffer; |
| 232 | |
| 233 | /* prepare the target variables */ |
| 234 | for (i = 0; i < pci->retc; i++) { |
| 235 | argv[i] = argrec + i; |
| 236 | argv[i]->vtype = getVarGDKType(mb, i); |
| 237 | } |
| 238 | |
| 239 | argc = m->argc; |
| 240 | parc = q->paramlen; |
| 241 | |
| 242 | if (argc != parc) { |
| 243 | if (pci->argc >= MAXARG && argv != argvbuffer) |
| 244 | GDKfree(argv); |
| 245 | if (pci->retc >= MAXARG && argrec != argrecbuffer) |
| 246 | GDKfree(argrec); |
| 247 | throw(SQL, "sql.prepare" , SQLSTATE(07001) "EXEC: wrong number of arguments for prepared statement: %d, expected %d" , argc, parc); |
| 248 | } else { |
| 249 | for (i = 0; i < m->argc; i++) { |
| 250 | atom *arg = m->args[i]; |
| 251 | sql_subtype *pt = q->params + i; |
| 252 | |
| 253 | if (!atom_cast(m->sa, arg, pt)) { |
| 254 | /*sql_error(c, 003, buf); */ |
| 255 | if (pci->argc >= MAXARG && argv != argvbuffer) |
| 256 | GDKfree(argv); |
| 257 | if (pci->retc >= MAXARG && argrec != argrecbuffer) |
| 258 | GDKfree(argrec); |
| 259 | throw(SQL, "sql.prepare" , SQLSTATE(07001) "EXEC: wrong type for argument %d of " "prepared statement: %s, expected %s" , i + 1, atom_type(arg)->type->sqlname, pt->type->sqlname); |
| 260 | } |
| 261 | argv[pci->retc + i] = &arg->data; |
| 262 | } |
| 263 | } |
| 264 | glb = (MalStkPtr) (q->stk); |
| 265 | ret = callMAL(c, mb, &glb, argv, (m->emod & mod_debug ? 'n' : 0)); |
| 266 | /* cleanup the arguments */ |
| 267 | for (i = pci->retc; i < pci->argc; i++) { |
| 268 | garbageElement(c, v = &glb->stk[pci->argv[i]]); |
| 269 | v->vtype = TYPE_int; |
| 270 | v->val.ival = int_nil; |
| 271 | } |
| 272 | q->stk = (backend_stack) glb; /* save garbageCollected stack */ |
| 273 | if (glb && SQLdebug & 1) |
| 274 | printStack(GDKstdout, mb, glb); |
| 275 | if (pci->argc >= MAXARG && argv != argvbuffer) |
| 276 | GDKfree(argv); |
| 277 | if (pci->retc >= MAXARG && argrec != argrecbuffer) |
| 278 | GDKfree(argrec); |
| 279 | return ret; |
| 280 | } |
| 281 | |
| 282 | static str |
| 283 | SQLrun(Client c, backend *be, mvc *m) |
| 284 | { |
| 285 | str msg= MAL_SUCCEED; |
| 286 | MalBlkPtr mc = 0, mb=c->curprg->def; |
| 287 | InstrPtr p=0; |
| 288 | int i,j, retc; |
| 289 | ValPtr val; |
| 290 | |
| 291 | if (*m->errstr){ |
| 292 | if (strlen(m->errstr) > 6 && m->errstr[5] == '!') |
| 293 | msg = createException(PARSE, "SQLparser" , "%s" , m->errstr); |
| 294 | else |
| 295 | msg = createException(PARSE, "SQLparser" , SQLSTATE(42000) "%s" , m->errstr); |
| 296 | *m->errstr=0; |
| 297 | return msg; |
| 298 | } |
| 299 | if (m->emode == m_execute && be->q->paramlen != m->argc) |
| 300 | throw(SQL, "sql.prepare" , SQLSTATE(42000) "EXEC called with wrong number of arguments: expected %d, got %d" , be->q->paramlen, m->argc); |
| 301 | MT_thread_setworking(m->query); |
| 302 | // locate and inline the query template instruction |
| 303 | mb = copyMalBlk(c->curprg->def); |
| 304 | if (!mb) { |
| 305 | MT_thread_setworking(NULL); |
| 306 | throw(SQL, "sql.prepare" , SQLSTATE(HY001) MAL_MALLOC_FAIL); |
| 307 | } |
| 308 | mb->history = c->curprg->def->history; |
| 309 | c->curprg->def->history = 0; |
| 310 | |
| 311 | /* only consider a re-optimization when we are dealing with query templates */ |
| 312 | for ( i= 1; i < mb->stop;i++){ |
| 313 | p = getInstrPtr(mb,i); |
| 314 | if( getFunctionId(p) && qc_isapreparedquerytemplate(getFunctionId(p) ) ){ |
| 315 | msg = SQLexecutePrepared(c, be, p->blk); |
| 316 | freeMalBlk(mb); |
| 317 | MT_thread_setworking(NULL); |
| 318 | return msg; |
| 319 | } |
| 320 | if( getFunctionId(p) && p->blk && qc_isaquerytemplate(getFunctionId(p)) ) { |
| 321 | mc = copyMalBlk(p->blk); |
| 322 | if (!mc) { |
| 323 | freeMalBlk(mb); |
| 324 | MT_thread_setworking(NULL); |
| 325 | throw(SQL, "sql.prepare" , SQLSTATE(HY001) MAL_MALLOC_FAIL); |
| 326 | } |
| 327 | retc = p->retc; |
| 328 | freeMalBlk(mb); // TODO can be factored out |
| 329 | mb = mc; |
| 330 | // declare the argument values as a constant |
| 331 | // We use the knowledge that the arguments are first on the stack |
| 332 | for (j = 0; j < m->argc; j++) { |
| 333 | sql_subtype *pt = be->q->params + j; |
| 334 | atom *arg = m->args[j]; |
| 335 | |
| 336 | if (!atom_cast(m->sa, arg, pt)) { |
| 337 | freeMalBlk(mb); |
| 338 | MT_thread_setworking(NULL); |
| 339 | throw(SQL, "sql.prepare" , SQLSTATE(07001) "EXEC: wrong type for argument %d of " "query template : %s, expected %s" , i + 1, atom_type(arg)->type->sqlname, pt->type->sqlname); |
| 340 | } |
| 341 | val= (ValPtr) &arg->data; |
| 342 | if (VALcopy(&mb->var[j+retc].value, val) == NULL){ |
| 343 | freeMalBlk(mb); |
| 344 | MT_thread_setworking(NULL); |
| 345 | throw(MAL, "sql.prepare" , SQLSTATE(HY001) MAL_MALLOC_FAIL); |
| 346 | } |
| 347 | setVarConstant(mb, j+retc); |
| 348 | setVarFixed(mb, j+retc); |
| 349 | } |
| 350 | mb->stmt[0]->argc = 1; |
| 351 | break; |
| 352 | } |
| 353 | } |
| 354 | // JIT optimize the SQL query using all current information |
| 355 | // This include template constants, BAT sizes. |
| 356 | if( m->emod & mod_debug) |
| 357 | mb->keephistory = TRUE; |
| 358 | msg = SQLoptimizeQuery(c, mb); |
| 359 | if( msg != MAL_SUCCEED){ |
| 360 | // freeMalBlk(mb); |
| 361 | MT_thread_setworking(NULL); |
| 362 | return msg; |
| 363 | } |
| 364 | mb->keephistory = FALSE; |
| 365 | |
| 366 | if (mb->errors){ |
| 367 | // freeMalBlk(mb); |
| 368 | // mal block might be so broken free causes segfault |
| 369 | msg = mb->errors; |
| 370 | mb->errors = 0; |
| 371 | MT_thread_setworking(NULL); |
| 372 | return msg; |
| 373 | } |
| 374 | |
| 375 | if (m->emod & mod_explain) { |
| 376 | if (c->curprg->def) |
| 377 | printFunction(c->fdout, mb, 0, LIST_MAL_NAME | LIST_MAL_VALUE | LIST_MAL_TYPE | LIST_MAL_MAPI); |
| 378 | } else if( m->emod & mod_debug) { |
| 379 | c->idle = 0; |
| 380 | c->lastcmd = time(0); |
| 381 | msg = runMALDebugger(c, mb); |
| 382 | } else { |
| 383 | if( m->emod & mod_trace){ |
| 384 | if((msg = SQLsetTrace(c,mb)) == MAL_SUCCEED) { |
| 385 | c->idle = 0; |
| 386 | c->lastcmd = time(0); |
| 387 | msg = runMAL(c, mb, 0, 0); |
| 388 | stopTrace(c); |
| 389 | } |
| 390 | } else { |
| 391 | c->idle = 0; |
| 392 | c->lastcmd = time(0); |
| 393 | msg = runMAL(c, mb, 0, 0); |
| 394 | } |
| 395 | } |
| 396 | /* after the query has been finished we enter the idle state */ |
| 397 | c->idle = time(0); |
| 398 | c->lastcmd = 0; |
| 399 | // release the resources |
| 400 | freeMalBlk(mb); |
| 401 | MT_thread_setworking(NULL); |
| 402 | return msg; |
| 403 | } |
| 404 | |
| 405 | /* |
| 406 | * Escape single quotes and backslashes. This is important to do before calling |
| 407 | * SQLstatementIntern, if we are pasting user provided strings into queries |
| 408 | * passed to the SQLstatementIntern. Otherwise we open ourselves to SQL |
| 409 | * injection attacks. |
| 410 | * |
| 411 | * It returns the input string with all the single quotes(') and the backslashes |
| 412 | * (\) doubled, or NULL, if it could not allocate enough space. |
| 413 | * |
| 414 | * The caller is responsible to free the returned value. |
| 415 | */ |
| 416 | str |
| 417 | SQLescapeString(str s) |
| 418 | { |
| 419 | str ret = NULL; |
| 420 | char *p, *q; |
| 421 | size_t len = 0; |
| 422 | |
| 423 | if(!s) { |
| 424 | return NULL; |
| 425 | } |
| 426 | |
| 427 | /* At most we will need 2*strlen(s) + 1 characters */ |
| 428 | len = strlen(s); |
| 429 | ret = (str)GDKmalloc(2*len + 1); |
| 430 | if (!ret) { |
| 431 | return NULL; |
| 432 | } |
| 433 | |
| 434 | for (p = s, q = ret; *p != '\0'; p++, q++) { |
| 435 | *q = *p; |
| 436 | if (*p == '\'') { |
| 437 | *(++q) = '\''; |
| 438 | } |
| 439 | else if (*p == '\\') { |
| 440 | *(++q) = '\\'; |
| 441 | } |
| 442 | } |
| 443 | |
| 444 | *q = '\0'; |
| 445 | return ret; |
| 446 | } |
| 447 | |
| 448 | str |
| 449 | SQLstatementIntern(Client c, str *expr, str nme, bit execute, bit output, res_table **result) |
| 450 | { |
| 451 | int status = 0; |
| 452 | int err = 0; |
| 453 | mvc *o, *m; |
| 454 | int ac, sizevars, topvars; |
| 455 | sql_var *vars; |
| 456 | int oldvtop, oldstop = 1; |
| 457 | buffer *b; |
| 458 | char *n; |
| 459 | bstream *bs; |
| 460 | stream *buf; |
| 461 | str msg = MAL_SUCCEED; |
| 462 | backend *be, *sql = (backend *) c->sqlcontext; |
| 463 | size_t len = strlen(*expr); |
| 464 | int inited = 0; |
| 465 | |
| 466 | #ifdef _SQL_COMPILE |
| 467 | mnstr_printf(c->fdout, "#SQLstatement:%s\n" , *expr); |
| 468 | #endif |
| 469 | if (!sql) { |
| 470 | inited = 1; |
| 471 | msg = SQLinitClient(c); |
| 472 | sql = (backend *) c->sqlcontext; |
| 473 | } |
| 474 | if (msg){ |
| 475 | freeException(msg); |
| 476 | throw(SQL, "sql.statement" , SQLSTATE(HY002) "Catalogue not available" ); |
| 477 | } |
| 478 | |
| 479 | m = sql->mvc; |
| 480 | ac = m->session->auto_commit; |
| 481 | o = MNEW(mvc); |
| 482 | if (!o) { |
| 483 | if (inited) |
| 484 | SQLresetClient(c); |
| 485 | throw(SQL, "sql.statement" , SQLSTATE(HY001) MAL_MALLOC_FAIL); |
| 486 | } |
| 487 | *o = *m; |
| 488 | /* hide query cache, this causes crashes in SQLtrans() due to uninitialized memory otherwise */ |
| 489 | m->qc = NULL; |
| 490 | |
| 491 | /* create private allocator */ |
| 492 | m->sa = NULL; |
| 493 | if ((msg = SQLtrans(m)) != MAL_SUCCEED) { |
| 494 | if (inited) |
| 495 | SQLresetClient(c); |
| 496 | return msg; |
| 497 | } |
| 498 | status = m->session->status; |
| 499 | |
| 500 | m->type = Q_PARSE; |
| 501 | be = sql; |
| 502 | sql = backend_create(m, c); |
| 503 | if( sql == NULL) |
| 504 | throw(SQL,"sql.statement" ,SQLSTATE(HY001) MAL_MALLOC_FAIL); |
| 505 | sql->output_format = be->output_format; |
| 506 | if (!output) { |
| 507 | sql->output_format = OFMT_NONE; |
| 508 | } |
| 509 | sql->depth++; |
| 510 | // and do it again |
| 511 | m->qc = NULL; |
| 512 | m->caching = 0; |
| 513 | m->user_id = m->role_id = USER_MONETDB; |
| 514 | if (result) |
| 515 | m->reply_size = -2; /* do not clean up result tables */ |
| 516 | |
| 517 | /* mimic a client channel on which the query text is received */ |
| 518 | b = (buffer *) GDKmalloc(sizeof(buffer)); |
| 519 | if( b == NULL) |
| 520 | throw(SQL,"sql.statement" , SQLSTATE(HY001) MAL_MALLOC_FAIL); |
| 521 | n = GDKmalloc(len + 1 + 1); |
| 522 | if( n == NULL) { |
| 523 | GDKfree(b); |
| 524 | throw(SQL,"sql.statement" , SQLSTATE(HY001) MAL_MALLOC_FAIL); |
| 525 | } |
| 526 | strncpy(n, *expr, len); |
| 527 | n[len] = '\n'; |
| 528 | n[len + 1] = 0; |
| 529 | len++; |
| 530 | buffer_init(b, n, len); |
| 531 | buf = buffer_rastream(b, "sqlstatement" ); |
| 532 | if(buf == NULL) { |
| 533 | buffer_destroy(b);//n and b will be freed by the buffer |
| 534 | throw(SQL,"sql.statement" ,SQLSTATE(HY001) MAL_MALLOC_FAIL); |
| 535 | } |
| 536 | bs = bstream_create(buf, b->len); |
| 537 | if(bs == NULL) { |
| 538 | buffer_destroy(b);//n and b will be freed by the buffer |
| 539 | throw(SQL,"sql.statement" ,SQLSTATE(HY001) MAL_MALLOC_FAIL); |
| 540 | } |
| 541 | scanner_init(&m->scanner, bs, NULL); |
| 542 | m->scanner.mode = LINE_N; |
| 543 | bstream_next(m->scanner.rs); |
| 544 | |
| 545 | m->params = NULL; |
| 546 | m->argc = 0; |
| 547 | m->session->auto_commit = 0; |
| 548 | if (!m->sa) |
| 549 | m->sa = sa_create(); |
| 550 | if (!m->sa) { |
| 551 | *m = *o; |
| 552 | _DELETE(o); |
| 553 | bstream_destroy(m->scanner.rs); |
| 554 | throw(SQL,"sql.statement" ,SQLSTATE(HY001) MAL_MALLOC_FAIL); |
| 555 | } |
| 556 | |
| 557 | /* |
| 558 | * System has been prepared to parse it and generate code. |
| 559 | * Scan the complete string for SQL statements, stop at the first error. |
| 560 | */ |
| 561 | c->sqlcontext = sql; |
| 562 | while (msg == MAL_SUCCEED && m->scanner.rs->pos < m->scanner.rs->len) { |
| 563 | sql_rel *r; |
| 564 | |
| 565 | if (!m->sa) |
| 566 | m->sa = sa_create(); |
| 567 | if (!m->sa) { |
| 568 | msg = createException(PARSE, "SQLparser" ,SQLSTATE(HY001) MAL_MALLOC_FAIL); |
| 569 | goto endofcompile; |
| 570 | } |
| 571 | m->sym = NULL; |
| 572 | if ((err = sqlparse(m)) || |
| 573 | /* Only forget old errors on transaction boundaries */ |
| 574 | (mvc_status(m) && m->type != Q_TRANS) || !m->sym) { |
| 575 | if (!err) |
| 576 | err = mvc_status(m); |
| 577 | if (*m->errstr){ |
| 578 | if (strlen(m->errstr) > 6 && m->errstr[5] == '!') |
| 579 | msg = createException(PARSE, "SQLparser" , "%s" , m->errstr); |
| 580 | else |
| 581 | msg = createException(PARSE, "SQLparser" , SQLSTATE(42000) "%s" , m->errstr); |
| 582 | *m->errstr = 0; |
| 583 | } |
| 584 | sqlcleanup(m, err); |
| 585 | execute = 0; |
| 586 | if (!err) |
| 587 | continue; |
| 588 | goto endofcompile; |
| 589 | } |
| 590 | |
| 591 | /* |
| 592 | * We have dealt with the first parsing step and advanced the input reader |
| 593 | * to the next statement (if any). |
| 594 | * Now is the time to also perform the semantic analysis, |
| 595 | * optimize and produce code. |
| 596 | * We don't search the cache for a previous incarnation yet. |
| 597 | */ |
| 598 | if((msg = MSinitClientPrg(c, "user" , nme)) != MAL_SUCCEED) { |
| 599 | goto endofcompile; |
| 600 | } |
| 601 | oldvtop = c->curprg->def->vtop; |
| 602 | oldstop = c->curprg->def->stop; |
| 603 | r = sql_symbol2relation(m, m->sym); |
| 604 | #ifdef _SQL_COMPILE |
| 605 | mnstr_printf(c->fdout, "#SQLstatement:\n" ); |
| 606 | #endif |
| 607 | scanner_query_processed(&(m->scanner)); |
| 608 | if ((err = mvc_status(m)) ) { |
| 609 | if (strlen(m->errstr) > 6 && m->errstr[5] == '!') |
| 610 | msg = createException(PARSE, "SQLparser" , "%s" , m->errstr); |
| 611 | else |
| 612 | msg = createException(PARSE, "SQLparser" , SQLSTATE(42000) "%s" , m->errstr); |
| 613 | *m->errstr=0; |
| 614 | msg = handle_error(m, status, msg); |
| 615 | sqlcleanup(m, err); |
| 616 | /* restore the state */ |
| 617 | MSresetInstructions(c->curprg->def, oldstop); |
| 618 | freeVariables(c, c->curprg->def, c->glb, oldvtop); |
| 619 | c->curprg->def->errors = 0; |
| 620 | goto endofcompile; |
| 621 | } |
| 622 | /* generate MAL code */ |
| 623 | #ifdef _SQL_COMPILE |
| 624 | mnstr_printf(c->fdout, "#SQLstatement:pre-compile\n" ); |
| 625 | printFunction(c->fdout, c->curprg->def, 0, LIST_MAL_NAME | LIST_MAL_VALUE | LIST_MAL_MAPI); |
| 626 | #endif |
| 627 | be->depth++; |
| 628 | if (backend_callinline(be, c) < 0 || |
| 629 | backend_dumpstmt(be, c->curprg->def, r, 1, 1, NULL) < 0) |
| 630 | err = 1; |
| 631 | be->depth--; |
| 632 | #ifdef _SQL_COMPILE |
| 633 | mnstr_printf(c->fdout, "#SQLstatement:post-compile\n" ); |
| 634 | printFunction(c->fdout, c->curprg->def, 0, LIST_MAL_NAME | LIST_MAL_VALUE | LIST_MAL_MAPI); |
| 635 | #endif |
| 636 | msg = SQLoptimizeFunction(c, c->curprg->def); |
| 637 | |
| 638 | if (err || c->curprg->def->errors || msg) { |
| 639 | /* restore the state */ |
| 640 | char *error = NULL; |
| 641 | MSresetInstructions(c->curprg->def, oldstop); |
| 642 | freeVariables(c, c->curprg->def, c->glb, oldvtop); |
| 643 | c->curprg->def->errors = 0; |
| 644 | if (strlen(m->errstr) > 6 && m->errstr[5] == '!') |
| 645 | error = createException(PARSE, "SQLparser" , "%s" , m->errstr); |
| 646 | else if (*m->errstr) |
| 647 | error = createException(PARSE, "SQLparser" , SQLSTATE(42000) "%s" , m->errstr); |
| 648 | else |
| 649 | error = createException(PARSE, "SQLparser" , SQLSTATE(42000) "%s" , msg); |
| 650 | if (msg) |
| 651 | freeException(msg); |
| 652 | msg = error; |
| 653 | *m->errstr = 0; |
| 654 | goto endofcompile; |
| 655 | } |
| 656 | #ifdef _SQL_COMPILE |
| 657 | mnstr_printf(c->fdout, "#result of sql.eval()\n" ); |
| 658 | printFunction(c->fdout, c->curprg->def, 0, c->listing); |
| 659 | #endif |
| 660 | |
| 661 | if (!output) |
| 662 | sql->out = NULL; /* no output stream */ |
| 663 | be->depth++; |
| 664 | if (execute) |
| 665 | msg = SQLrun(c,be,m); |
| 666 | be->depth--; |
| 667 | MSresetInstructions(c->curprg->def, oldstop); |
| 668 | freeVariables(c, c->curprg->def, NULL, oldvtop); |
| 669 | sqlcleanup(m, 0); |
| 670 | if (!execute) |
| 671 | goto endofcompile; |
| 672 | #ifdef _SQL_COMPILE |
| 673 | mnstr_printf(c->fdout, "#parse/execute result %d\n" , err); |
| 674 | #endif |
| 675 | } |
| 676 | if (m->results) { |
| 677 | if (result) { /* return all results sets */ |
| 678 | *result = m->results; |
| 679 | } else { |
| 680 | if (m->results == o->results) |
| 681 | o->results = NULL; |
| 682 | res_tables_destroy(m->results); |
| 683 | } |
| 684 | m->results = NULL; |
| 685 | } |
| 686 | /* |
| 687 | * We are done; a MAL procedure resides in the cache. |
| 688 | */ |
| 689 | endofcompile: |
| 690 | if (execute) |
| 691 | MSresetInstructions(c->curprg->def, 1); |
| 692 | |
| 693 | c->sqlcontext = be; |
| 694 | backend_destroy(sql); |
| 695 | GDKfree(n); |
| 696 | GDKfree(b); |
| 697 | bstream_destroy(m->scanner.rs); |
| 698 | if (m->sa) |
| 699 | sa_destroy(m->sa); |
| 700 | m->sa = NULL; |
| 701 | m->sym = NULL; |
| 702 | /* variable stack maybe resized, ie we need to keep the new stack */ |
| 703 | status = m->session->status; |
| 704 | sizevars = m->sizevars; |
| 705 | topvars = m->topvars; |
| 706 | vars = m->vars; |
| 707 | *m = *o; |
| 708 | _DELETE(o); |
| 709 | m->sizevars = sizevars; |
| 710 | m->topvars = topvars; |
| 711 | m->vars = vars; |
| 712 | m->session->status = status; |
| 713 | m->session->auto_commit = ac; |
| 714 | if (inited) |
| 715 | SQLresetClient(c); |
| 716 | return msg; |
| 717 | } |
| 718 | |
| 719 | str |
| 720 | SQLengineIntern(Client c, backend *be) |
| 721 | { |
| 722 | str msg = MAL_SUCCEED; |
| 723 | char oldlang = be->language; |
| 724 | mvc *m = be->mvc; |
| 725 | |
| 726 | if (oldlang == 'X') { /* return directly from X-commands */ |
| 727 | sqlcleanup(be->mvc, 0); |
| 728 | return MAL_SUCCEED; |
| 729 | } |
| 730 | |
| 731 | #ifdef SQL_SCENARIO_DEBUG |
| 732 | fprintf(stderr, "#Ready to execute SQL statement\n" ); |
| 733 | #endif |
| 734 | |
| 735 | if (c->curprg->def->stop == 1) { |
| 736 | if (mvc_status(m)) { |
| 737 | if (*m->errstr){ |
| 738 | if (strlen(m->errstr) > 6 && m->errstr[5] == '!') |
| 739 | msg = createException(PARSE, "SQLparser" , "%s" , m->errstr); |
| 740 | else |
| 741 | msg = createException(PARSE, "SQLparser" , SQLSTATE(42000) "%s" , m->errstr); |
| 742 | *m->errstr = 0; |
| 743 | } |
| 744 | goto cleanup_engine; |
| 745 | } |
| 746 | sqlcleanup(be->mvc, 0); |
| 747 | return MAL_SUCCEED; |
| 748 | } |
| 749 | |
| 750 | if (m->emode == m_prepare) |
| 751 | goto cleanup_engine; |
| 752 | |
| 753 | be->language = 'D'; |
| 754 | /* |
| 755 | * The code below is copied from MALengine, which handles execution |
| 756 | * in the context of a user global environment. We have a private |
| 757 | * environment. |
| 758 | */ |
| 759 | if (MALcommentsOnly(c->curprg->def)) |
| 760 | msg = MAL_SUCCEED; |
| 761 | else |
| 762 | msg = SQLrun(c,be,m); |
| 763 | |
| 764 | cleanup_engine: |
| 765 | if (m->type == Q_SCHEMA && m->qc != NULL) |
| 766 | qc_clean(m->qc); |
| 767 | if (msg) { |
| 768 | /* don't print exception decoration, just the message */ |
| 769 | /* |
| 770 | char *n = NULL; |
| 771 | char *o = msg; |
| 772 | while ((n = strchr(o, '\n')) != NULL) { |
| 773 | *n = '\0'; |
| 774 | mnstr_printf(c->fdout, "!%s\n", getExceptionMessage(o)); |
| 775 | *n++ = '\n'; |
| 776 | o = n; |
| 777 | } |
| 778 | if (*o != 0) |
| 779 | mnstr_printf(c->fdout, "!%s\n", getExceptionMessage(o)); |
| 780 | */ |
| 781 | m->session->status = -10; |
| 782 | } |
| 783 | |
| 784 | if (m->type != Q_SCHEMA && be->q && msg) { |
| 785 | qc_delete(m->qc, be->q); |
| 786 | } |
| 787 | be->q = NULL; |
| 788 | sqlcleanup(be->mvc, (!msg) ? 0 : -1); |
| 789 | MSresetInstructions(c->curprg->def, 1); |
| 790 | freeVariables(c, c->curprg->def, NULL, be->vtop); |
| 791 | be->language = oldlang; |
| 792 | /* |
| 793 | * Any error encountered during execution should block further processing |
| 794 | * unless auto_commit has been set. |
| 795 | */ |
| 796 | return msg; |
| 797 | } |
| 798 | |
| 799 | void SQLdestroyResult(res_table *destroy) { |
| 800 | res_table_destroy(destroy); |
| 801 | } |
| 802 | |
| 803 | /* a hook is provided to execute relational algebra expressions */ |
| 804 | str |
| 805 | RAstatement(Client c, MalBlkPtr mb, MalStkPtr stk, InstrPtr pci) |
| 806 | { |
| 807 | int pos = 0; |
| 808 | str *expr = getArgReference_str(stk, pci, 1); |
| 809 | bit *opt = getArgReference_bit(stk, pci, 2); |
| 810 | backend *b = NULL; |
| 811 | mvc *m = NULL; |
| 812 | str msg; |
| 813 | sql_rel *rel; |
| 814 | list *refs; |
| 815 | |
| 816 | if ((msg = getSQLContext(c, mb, &m, &b)) != NULL) |
| 817 | return msg; |
| 818 | if ((msg = checkSQLContext(c)) != NULL) |
| 819 | return msg; |
| 820 | if ((msg = SQLtrans(m)) != MAL_SUCCEED) |
| 821 | return msg; |
| 822 | if (!m->sa) |
| 823 | m->sa = sa_create(); |
| 824 | if (!m->sa) |
| 825 | return createException(SQL,"RAstatement" ,SQLSTATE(HY001) MAL_MALLOC_FAIL); |
| 826 | refs = sa_list(m->sa); |
| 827 | rel = rel_read(m, *expr, &pos, refs); |
| 828 | if (rel) { |
| 829 | int oldvtop = c->curprg->def->vtop; |
| 830 | int oldstop = c->curprg->def->stop; |
| 831 | |
| 832 | if (*opt) { |
| 833 | rel = rel_unnest(m, rel); |
| 834 | rel = rel_optimizer(m, rel, 0); |
| 835 | } |
| 836 | |
| 837 | if ((msg = MSinitClientPrg(c, "user" , "test" )) != MAL_SUCCEED) { |
| 838 | rel_destroy(rel); |
| 839 | return msg; |
| 840 | } |
| 841 | |
| 842 | /* generate MAL code, ignoring any code generation error */ |
| 843 | if (backend_callinline(b, c) < 0 || |
| 844 | backend_dumpstmt(b, c->curprg->def, rel, 1, 1, NULL) < 0) { |
| 845 | msg = createException(SQL,"RAstatement" ,"Program contains errors" ); // TODO: use macro definition. |
| 846 | } else { |
| 847 | SQLaddQueryToCache(c); |
| 848 | msg = SQLoptimizeFunction(c,c->curprg->def); |
| 849 | } |
| 850 | rel_destroy(rel); |
| 851 | if( msg == MAL_SUCCEED) |
| 852 | msg = SQLrun(c,b,m); |
| 853 | if (!msg) { |
| 854 | resetMalBlk(c->curprg->def, oldstop); |
| 855 | freeVariables(c, c->curprg->def, NULL, oldvtop); |
| 856 | } |
| 857 | if (!msg) |
| 858 | msg = mvc_commit(m, 0, NULL, false); |
| 859 | else |
| 860 | msg = mvc_rollback(m, 0, NULL, false); |
| 861 | } |
| 862 | return msg; |
| 863 | } |
| 864 | |
| 865 | static int |
| 866 | is_a_number(char *v) |
| 867 | { |
| 868 | while(*v) { |
| 869 | if (!isdigit((unsigned char) *v)) |
| 870 | return 0; |
| 871 | v++; |
| 872 | } |
| 873 | return 1; |
| 874 | } |
| 875 | |
| 876 | str |
| 877 | RAstatement2(Client cntxt, MalBlkPtr mb, MalStkPtr stk, InstrPtr pci) |
| 878 | { |
| 879 | int pos = 0; |
| 880 | str *mod = getArgReference_str(stk, pci, 1); |
| 881 | str *nme = getArgReference_str(stk, pci, 2); |
| 882 | str *expr = getArgReference_str(stk, pci, 3); |
| 883 | str *sig = getArgReference_str(stk, pci, 4), c = *sig; |
| 884 | backend *be = NULL; |
| 885 | mvc *m = NULL; |
| 886 | str msg; |
| 887 | sql_rel *rel; |
| 888 | list *refs, *ops; |
| 889 | char buf[BUFSIZ]; |
| 890 | |
| 891 | if ((msg = getSQLContext(cntxt, mb, &m, &be)) != NULL) |
| 892 | return msg; |
| 893 | if ((msg = checkSQLContext(cntxt)) != NULL) |
| 894 | return msg; |
| 895 | if ((msg = SQLtrans(m)) != MAL_SUCCEED) |
| 896 | return msg; |
| 897 | if (!m->sa) |
| 898 | m->sa = sa_create(); |
| 899 | if (!m->sa) |
| 900 | return createException(SQL,"RAstatement2" ,SQLSTATE(HY001) MAL_MALLOC_FAIL); |
| 901 | |
| 902 | /* keep copy of signature and relational expression */ |
| 903 | snprintf(buf, BUFSIZ, "%s %s" , *sig, *expr); |
| 904 | |
| 905 | if(!stack_push_frame(m, NULL)) |
| 906 | return createException(SQL,"RAstatement2" ,SQLSTATE(HY001) MAL_MALLOC_FAIL); |
| 907 | ops = sa_list(m->sa); |
| 908 | while (c && *c && !isspace((unsigned char) *c)) { |
| 909 | char *vnme = c, *tnme; |
| 910 | char *p = strchr(++c, (int)' '); |
| 911 | int d,s,nr = -1; |
| 912 | sql_subtype t; |
| 913 | atom *a; |
| 914 | |
| 915 | *p++ = 0; |
| 916 | /* vnme can be name or number */ |
| 917 | if (is_a_number(vnme+1)) |
| 918 | nr = strtol(vnme+1, NULL, 10); |
| 919 | tnme = p; |
| 920 | p = strchr(p, (int)'('); |
| 921 | *p++ = 0; |
| 922 | tnme = sa_strdup(m->sa, tnme); |
| 923 | if (!tnme) { |
| 924 | stack_pop_frame(m); |
| 925 | return createException(SQL,"RAstatement2" ,SQLSTATE(HY001) MAL_MALLOC_FAIL); |
| 926 | } |
| 927 | d = strtol(p, &p, 10); |
| 928 | p++; /* skip , */ |
| 929 | s = strtol(p, &p, 10); |
| 930 | |
| 931 | sql_find_subtype(&t, tnme, d, s); |
| 932 | a = atom_general(m->sa, &t, NULL); |
| 933 | /* the argument list may have holes and maybe out of order, ie |
| 934 | * don't use sql_add_arg, but special numbered version |
| 935 | * sql_set_arg(m, a, nr); |
| 936 | * */ |
| 937 | if (nr >= 0) { |
| 938 | append(ops, exp_atom_ref(m->sa, nr, &t)); |
| 939 | if(!sql_set_arg(m, nr, a)) { |
| 940 | stack_pop_frame(m); |
| 941 | return createException(SQL,"RAstatement2" ,SQLSTATE(HY001) MAL_MALLOC_FAIL); |
| 942 | } |
| 943 | } else { |
| 944 | if(!stack_push_var(m, vnme+1, &t)) { |
| 945 | stack_pop_frame(m); |
| 946 | return createException(SQL,"RAstatement2" ,SQLSTATE(HY001) MAL_MALLOC_FAIL); |
| 947 | } |
| 948 | append(ops, exp_var(m->sa, sa_strdup(m->sa, vnme+1), &t, m->frame)); |
| 949 | } |
| 950 | c = strchr(p, (int)','); |
| 951 | if (c) |
| 952 | c++; |
| 953 | } |
| 954 | refs = sa_list(m->sa); |
| 955 | rel = rel_read(m, *expr, &pos, refs); |
| 956 | stack_pop_frame(m); |
| 957 | if (rel) |
| 958 | rel = rel_unnest(m, rel); |
| 959 | if (rel) |
| 960 | rel = rel_optimizer(m, rel, 0); |
| 961 | if (!rel || monet5_create_relational_function(m, *mod, *nme, rel, NULL, ops, 0) < 0) |
| 962 | throw(SQL, "sql.register" , SQLSTATE(42000) "Cannot register %s" , buf); |
| 963 | rel_destroy(rel); |
| 964 | sqlcleanup(m, 0); |
| 965 | return msg; |
| 966 | } |
| 967 | |