1 /* 2 Copyright (c) 2005, 2010, Oracle and/or its affiliates. 3 4 This program is free software; you can redistribute it and/or modify 5 it under the terms of the GNU General Public License as published by 6 the Free Software Foundation; version 2 of the License. 7 8 This program is distributed in the hope that it will be useful, 9 but WITHOUT ANY WARRANTY; without even the implied warranty of 10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 GNU General Public License for more details. 12 13 You should have received a copy of the GNU General Public License 14 along with this program; if not, write to the Free Software 15 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA */ 16 #ifdef USE_PRAGMA_IMPLEMENTATION 17 #pragma implementation /* gcc class implementation */ 18 #endif 19 20 #include "mariadb.h" 21 #include "sql_priv.h" 22 #include "unireg.h" 23 #include "sql_cursor.h" 24 #include "probes_mysql.h" 25 #include "sql_parse.h" // mysql_execute_command 26 27 /**************************************************************************** 28 Declarations. 29 ****************************************************************************/ 30 31 /** 32 Materialized_cursor -- an insensitive materialized server-side 33 cursor. The result set of this cursor is saved in a temporary 34 table at open. The cursor itself is simply an interface for the 35 handler of the temporary table. 36 */ 37 38 class Materialized_cursor: public Server_side_cursor 39 { 40 MEM_ROOT main_mem_root; 41 /* A fake unit to supply to select_send when fetching */ 42 SELECT_LEX_UNIT fake_unit; 43 TABLE *table; 44 List<Item> item_list; 45 ulong fetch_limit; 46 ulong fetch_count; 47 bool is_rnd_inited; 48 public: 49 Materialized_cursor(select_result *result, TABLE *table); 50 51 int send_result_set_metadata(THD *thd, List<Item> &send_result_set_metadata); 52 virtual bool is_open() const { return table != 0; } 53 virtual int open(JOIN *join __attribute__((unused))); 54 virtual void fetch(ulong num_rows); 55 virtual void close(); 56 bool export_structure(THD *thd, Row_definition_list *defs) 57 { 58 return table->export_structure(thd, defs); 59 } 60 virtual ~Materialized_cursor(); 61 62 void on_table_fill_finished(); 63 }; 64 65 66 /** 67 Select_materialize -- a mediator between a cursor query and the 68 protocol. In case we were not able to open a non-materialzed 69 cursor, it creates an internal temporary HEAP table, and insert 70 all rows into it. When the table reaches max_heap_table_size, 71 it's converted to a MyISAM table. Later this table is used to 72 create a Materialized_cursor. 73 */ 74 75 class Select_materialize: public select_unit 76 { 77 select_result *result; /**< the result object of the caller (PS or SP) */ 78 public: 79 Materialized_cursor *materialized_cursor; 80 Select_materialize(THD *thd_arg, select_result *result_arg): 81 select_unit(thd_arg), result(result_arg), materialized_cursor(0) {} 82 virtual bool send_result_set_metadata(List<Item> &list, uint flags); 83 bool send_eof() 84 { 85 if (materialized_cursor) 86 materialized_cursor->on_table_fill_finished(); 87 return false; 88 } 89 90 void abort_result_set() 91 { 92 if (materialized_cursor) 93 materialized_cursor->on_table_fill_finished(); 94 } 95 96 bool view_structure_only() const 97 { 98 return result->view_structure_only(); 99 } 100 }; 101 102 103 /**************************************************************************/ 104 105 /** 106 Attempt to open a materialized cursor. 107 108 @param thd thread handle 109 @param[in] result result class of the caller used as a destination 110 for the rows fetched from the cursor 111 @param[out] pcursor a pointer to store a pointer to cursor in 112 113 @retval 114 0 the query has been successfully executed; in this 115 case pcursor may or may not contain 116 a pointer to an open cursor. 117 @retval 118 non-zero an error, 'pcursor' has been left intact. 119 */ 120 121 int mysql_open_cursor(THD *thd, select_result *result, 122 Server_side_cursor **pcursor) 123 { 124 sql_digest_state *parent_digest; 125 PSI_statement_locker *parent_locker; 126 select_result *save_result; 127 Select_materialize *result_materialize; 128 LEX *lex= thd->lex; 129 int rc; 130 131 if (!(result_materialize= new (thd->mem_root) Select_materialize(thd, result))) 132 return 1; 133 134 save_result= lex->result; 135 136 lex->result= result_materialize; 137 138 MYSQL_QUERY_EXEC_START(thd->query(), 139 thd->thread_id, 140 thd->get_db(), 141 &thd->security_ctx->priv_user[0], 142 (char *) thd->security_ctx->host_or_ip, 143 2); 144 parent_digest= thd->m_digest; 145 parent_locker= thd->m_statement_psi; 146 thd->m_digest= NULL; 147 thd->m_statement_psi= NULL; 148 /* Mark that we can't use query cache with cursors */ 149 thd->query_cache_is_applicable= 0; 150 rc= mysql_execute_command(thd); 151 thd->lex->restore_set_statement_var(); 152 thd->m_digest= parent_digest; 153 thd->m_statement_psi= parent_locker; 154 MYSQL_QUERY_EXEC_DONE(rc); 155 156 lex->result= save_result; 157 /* 158 Possible options here: 159 - a materialized cursor is open. In this case rc is 0 and 160 result_materialize->materialized is not NULL 161 - an error occurred during materialization. 162 result_materialize->materialized_cursor is not NULL, but rc != 0 163 - successful completion of mysql_execute_command without 164 a cursor: rc is 0, result_materialize->materialized_cursor is NULL. 165 This is possible if some command writes directly to the 166 network, bypassing select_result mechanism. An example of 167 such command is SHOW VARIABLES or SHOW STATUS. 168 */ 169 if (rc) 170 { 171 if (result_materialize->materialized_cursor) 172 { 173 /* Rollback metadata in the client-server protocol. */ 174 result_materialize->abort_result_set(); 175 176 delete result_materialize->materialized_cursor; 177 } 178 179 goto end; 180 } 181 182 if (result_materialize->materialized_cursor) 183 { 184 Materialized_cursor *materialized_cursor= 185 result_materialize->materialized_cursor; 186 187 /* 188 NOTE: close_thread_tables() has been called in 189 mysql_execute_command(), so all tables except from the cursor 190 temporary table have been closed. 191 */ 192 193 if ((rc= materialized_cursor->open(0))) 194 { 195 delete materialized_cursor; 196 goto end; 197 } 198 199 *pcursor= materialized_cursor; 200 thd->stmt_arena->cleanup_stmt(); 201 } 202 203 end: 204 delete result_materialize; 205 return rc; 206 } 207 208 /**************************************************************************** 209 Server_side_cursor 210 ****************************************************************************/ 211 212 Server_side_cursor::~Server_side_cursor() 213 { 214 } 215 216 217 void Server_side_cursor::operator delete(void *ptr, size_t size) 218 { 219 Server_side_cursor *cursor= (Server_side_cursor*) ptr; 220 MEM_ROOT own_root= *cursor->mem_root; 221 222 DBUG_ENTER("Server_side_cursor::operator delete"); 223 TRASH_FREE(ptr, size); 224 /* 225 If this cursor has never been opened mem_root is empty. Otherwise 226 mem_root points to the memory the cursor object was allocated in. 227 In this case it's important to call free_root last, and free a copy 228 instead of *mem_root to avoid writing into freed memory. 229 */ 230 free_root(&own_root, MYF(0)); 231 DBUG_VOID_RETURN; 232 } 233 234 235 /*************************************************************************** 236 Materialized_cursor 237 ****************************************************************************/ 238 239 Materialized_cursor::Materialized_cursor(select_result *result_arg, 240 TABLE *table_arg) 241 :Server_side_cursor(&table_arg->mem_root, result_arg), 242 table(table_arg), 243 fetch_limit(0), 244 fetch_count(0), 245 is_rnd_inited(0) 246 { 247 fake_unit.init_query(); 248 fake_unit.thd= table->in_use; 249 } 250 251 252 /** 253 Preserve the original metadata to be sent to the client. 254 Initiate sending of the original metadata to the client 255 (call Protocol::send_result_set_metadata()). 256 257 @param thd Thread identifier. 258 @param send_result_set_metadata List of fields that would be sent. 259 */ 260 261 int Materialized_cursor::send_result_set_metadata( 262 THD *thd, List<Item> &send_result_set_metadata) 263 { 264 Query_arena backup_arena; 265 int rc; 266 List_iterator_fast<Item> it_org(send_result_set_metadata); 267 List_iterator_fast<Item> it_dst(item_list); 268 Item *item_org; 269 Item *item_dst; 270 271 thd->set_n_backup_active_arena(this, &backup_arena); 272 273 if ((rc= table->fill_item_list(&item_list))) 274 goto end; 275 276 DBUG_ASSERT(send_result_set_metadata.elements == item_list.elements); 277 278 /* 279 Unless we preserve the original metadata, it will be lost, 280 since new fields describe columns of the temporary table. 281 Allocate a copy of the name for safety only. Currently 282 items with original names are always kept in memory, 283 but in case this changes a memory leak may be hard to notice. 284 */ 285 while ((item_dst= it_dst++, item_org= it_org++)) 286 { 287 Send_field send_field; 288 Item_ident *ident= static_cast<Item_ident *>(item_dst); 289 item_org->make_send_field(thd, &send_field); 290 291 ident->db_name= thd->strdup(send_field.db_name); 292 ident->table_name= thd->strdup(send_field.table_name); 293 } 294 295 /* 296 Original metadata result set should be sent here. After 297 mysql_execute_command() is finished, item_list can not be used for 298 sending metadata, because it references closed table. 299 */ 300 rc= result->send_result_set_metadata(item_list, Protocol::SEND_NUM_ROWS); 301 302 end: 303 thd->restore_active_arena(this, &backup_arena); 304 /* Check for thd->is_error() in case of OOM */ 305 return rc || thd->is_error(); 306 } 307 308 309 int Materialized_cursor::open(JOIN *join __attribute__((unused))) 310 { 311 THD *thd= fake_unit.thd; 312 int rc; 313 Query_arena backup_arena; 314 315 thd->set_n_backup_active_arena(this, &backup_arena); 316 317 /* Create a list of fields and start sequential scan. */ 318 319 rc= result->prepare(item_list, &fake_unit); 320 rc= !rc && table->file->ha_rnd_init_with_error(TRUE); 321 is_rnd_inited= !rc; 322 323 thd->restore_active_arena(this, &backup_arena); 324 325 /* Commit or rollback metadata in the client-server protocol. */ 326 327 if (!rc) 328 { 329 thd->server_status|= SERVER_STATUS_CURSOR_EXISTS; 330 result->send_eof(); 331 } 332 else 333 { 334 result->abort_result_set(); 335 } 336 337 return rc; 338 } 339 340 341 /** 342 Fetch up to the given number of rows from a materialized cursor. 343 344 Precondition: the cursor is open. 345 346 If the cursor points after the last row, the fetch will automatically 347 close the cursor and not send any data (except the 'EOF' packet 348 with SERVER_STATUS_LAST_ROW_SENT). This is an extra round trip 349 and probably should be improved to return 350 SERVER_STATUS_LAST_ROW_SENT along with the last row. 351 */ 352 353 void Materialized_cursor::fetch(ulong num_rows) 354 { 355 THD *thd= table->in_use; 356 357 int res= 0; 358 result->begin_dataset(); 359 for (fetch_limit+= num_rows; fetch_count < fetch_limit; fetch_count++) 360 { 361 if ((res= table->file->ha_rnd_next(table->record[0]))) 362 break; 363 /* Send data only if the read was successful. */ 364 /* 365 If network write failed (i.e. due to a closed socked), 366 the error has already been set. Just return. 367 */ 368 if (result->send_data(item_list) > 0) 369 return; 370 } 371 372 switch (res) { 373 case 0: 374 thd->server_status|= SERVER_STATUS_CURSOR_EXISTS; 375 result->send_eof(); 376 break; 377 case HA_ERR_END_OF_FILE: 378 thd->server_status|= SERVER_STATUS_LAST_ROW_SENT; 379 result->send_eof(); 380 close(); 381 break; 382 default: 383 table->file->print_error(res, MYF(0)); 384 close(); 385 break; 386 } 387 } 388 389 390 void Materialized_cursor::close() 391 { 392 /* Free item_list items */ 393 free_items(); 394 if (is_rnd_inited) 395 (void) table->file->ha_rnd_end(); 396 /* 397 We need to grab table->mem_root to prevent free_tmp_table from freeing: 398 the cursor object was allocated in this memory. 399 */ 400 main_mem_root= table->mem_root; 401 mem_root= &main_mem_root; 402 clear_alloc_root(&table->mem_root); 403 free_tmp_table(table->in_use, table); 404 table= 0; 405 } 406 407 408 Materialized_cursor::~Materialized_cursor() 409 { 410 if (is_open()) 411 close(); 412 } 413 414 415 /* 416 @brief 417 Perform actions that are to be done when cursor materialization has 418 finished. 419 420 @detail 421 This function is called when "OPEN $cursor" has finished filling the 422 temporary table with rows that the cursor will return. 423 424 Temporary table has table->field->orig_table pointing at the tables 425 that are used in the cursor definition query. Pointers to these tables 426 will not be valid after the query finishes. So, we do what is done for 427 regular tables: have orig_table point at the table that the fields belong 428 to. 429 */ 430 431 void Materialized_cursor::on_table_fill_finished() 432 { 433 uint fields= table->s->fields; 434 for (uint i= 0; i < fields; i++) 435 table->field[i]->orig_table= table->field[i]->table; 436 } 437 438 /*************************************************************************** 439 Select_materialize 440 ****************************************************************************/ 441 442 bool Select_materialize::send_result_set_metadata(List<Item> &list, uint flags) 443 { 444 DBUG_ASSERT(table == 0); 445 if (create_result_table(unit->thd, unit->get_column_types(true), 446 FALSE, 447 thd->variables.option_bits | TMP_TABLE_ALL_COLUMNS, 448 &empty_clex_str, FALSE, TRUE, TRUE, 0)) 449 return TRUE; 450 451 materialized_cursor= new (&table->mem_root) 452 Materialized_cursor(result, table); 453 454 if (!materialized_cursor) 455 { 456 free_tmp_table(table->in_use, table); 457 table= 0; 458 return TRUE; 459 } 460 461 if (materialized_cursor->send_result_set_metadata(unit->thd, list)) 462 { 463 delete materialized_cursor; 464 table= 0; 465 materialized_cursor= 0; 466 return TRUE; 467 } 468 469 return FALSE; 470 } 471 472