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-1301, 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 | |
97 | |
98 | /**************************************************************************/ |
99 | |
100 | /** |
101 | Attempt to open a materialized cursor. |
102 | |
103 | @param thd thread handle |
104 | @param[in] result result class of the caller used as a destination |
105 | for the rows fetched from the cursor |
106 | @param[out] pcursor a pointer to store a pointer to cursor in |
107 | |
108 | @retval |
109 | 0 the query has been successfully executed; in this |
110 | case pcursor may or may not contain |
111 | a pointer to an open cursor. |
112 | @retval |
113 | non-zero an error, 'pcursor' has been left intact. |
114 | */ |
115 | |
116 | int mysql_open_cursor(THD *thd, select_result *result, |
117 | Server_side_cursor **pcursor) |
118 | { |
119 | sql_digest_state *parent_digest; |
120 | PSI_statement_locker *parent_locker; |
121 | select_result *save_result; |
122 | Select_materialize *result_materialize; |
123 | LEX *lex= thd->lex; |
124 | int rc; |
125 | |
126 | if (!(result_materialize= new (thd->mem_root) Select_materialize(thd, result))) |
127 | return 1; |
128 | |
129 | save_result= lex->result; |
130 | |
131 | lex->result= result_materialize; |
132 | |
133 | MYSQL_QUERY_EXEC_START(thd->query(), |
134 | thd->thread_id, |
135 | thd->get_db(), |
136 | &thd->security_ctx->priv_user[0], |
137 | (char *) thd->security_ctx->host_or_ip, |
138 | 2); |
139 | parent_digest= thd->m_digest; |
140 | parent_locker= thd->m_statement_psi; |
141 | thd->m_digest= NULL; |
142 | thd->m_statement_psi= NULL; |
143 | /* Mark that we can't use query cache with cursors */ |
144 | thd->query_cache_is_applicable= 0; |
145 | rc= mysql_execute_command(thd); |
146 | thd->lex->restore_set_statement_var(); |
147 | thd->m_digest= parent_digest; |
148 | thd->m_statement_psi= parent_locker; |
149 | MYSQL_QUERY_EXEC_DONE(rc); |
150 | |
151 | lex->result= save_result; |
152 | /* |
153 | Possible options here: |
154 | - a materialized cursor is open. In this case rc is 0 and |
155 | result_materialize->materialized is not NULL |
156 | - an error occurred during materialization. |
157 | result_materialize->materialized_cursor is not NULL, but rc != 0 |
158 | - successful completion of mysql_execute_command without |
159 | a cursor: rc is 0, result_materialize->materialized_cursor is NULL. |
160 | This is possible if some command writes directly to the |
161 | network, bypassing select_result mechanism. An example of |
162 | such command is SHOW VARIABLES or SHOW STATUS. |
163 | */ |
164 | if (rc) |
165 | { |
166 | if (result_materialize->materialized_cursor) |
167 | { |
168 | /* Rollback metadata in the client-server protocol. */ |
169 | result_materialize->abort_result_set(); |
170 | |
171 | delete result_materialize->materialized_cursor; |
172 | } |
173 | |
174 | goto end; |
175 | } |
176 | |
177 | if (result_materialize->materialized_cursor) |
178 | { |
179 | Materialized_cursor *materialized_cursor= |
180 | result_materialize->materialized_cursor; |
181 | |
182 | /* |
183 | NOTE: close_thread_tables() has been called in |
184 | mysql_execute_command(), so all tables except from the cursor |
185 | temporary table have been closed. |
186 | */ |
187 | |
188 | if ((rc= materialized_cursor->open(0))) |
189 | { |
190 | delete materialized_cursor; |
191 | goto end; |
192 | } |
193 | |
194 | *pcursor= materialized_cursor; |
195 | thd->stmt_arena->cleanup_stmt(); |
196 | } |
197 | |
198 | end: |
199 | delete result_materialize; |
200 | return rc; |
201 | } |
202 | |
203 | /**************************************************************************** |
204 | Server_side_cursor |
205 | ****************************************************************************/ |
206 | |
207 | Server_side_cursor::~Server_side_cursor() |
208 | { |
209 | } |
210 | |
211 | |
212 | void Server_side_cursor::operator delete(void *ptr, size_t size) |
213 | { |
214 | Server_side_cursor *cursor= (Server_side_cursor*) ptr; |
215 | MEM_ROOT own_root= *cursor->mem_root; |
216 | |
217 | DBUG_ENTER("Server_side_cursor::operator delete" ); |
218 | TRASH_FREE(ptr, size); |
219 | /* |
220 | If this cursor has never been opened mem_root is empty. Otherwise |
221 | mem_root points to the memory the cursor object was allocated in. |
222 | In this case it's important to call free_root last, and free a copy |
223 | instead of *mem_root to avoid writing into freed memory. |
224 | */ |
225 | free_root(&own_root, MYF(0)); |
226 | DBUG_VOID_RETURN; |
227 | } |
228 | |
229 | |
230 | /*************************************************************************** |
231 | Materialized_cursor |
232 | ****************************************************************************/ |
233 | |
234 | Materialized_cursor::Materialized_cursor(select_result *result_arg, |
235 | TABLE *table_arg) |
236 | :Server_side_cursor(&table_arg->mem_root, result_arg), |
237 | table(table_arg), |
238 | fetch_limit(0), |
239 | fetch_count(0), |
240 | is_rnd_inited(0) |
241 | { |
242 | fake_unit.init_query(); |
243 | fake_unit.thd= table->in_use; |
244 | } |
245 | |
246 | |
247 | /** |
248 | Preserve the original metadata to be sent to the client. |
249 | Initiate sending of the original metadata to the client |
250 | (call Protocol::send_result_set_metadata()). |
251 | |
252 | @param thd Thread identifier. |
253 | @param send_result_set_metadata List of fields that would be sent. |
254 | */ |
255 | |
256 | int Materialized_cursor::send_result_set_metadata( |
257 | THD *thd, List<Item> &send_result_set_metadata) |
258 | { |
259 | Query_arena backup_arena; |
260 | int rc; |
261 | List_iterator_fast<Item> it_org(send_result_set_metadata); |
262 | List_iterator_fast<Item> it_dst(item_list); |
263 | Item *item_org; |
264 | Item *item_dst; |
265 | |
266 | thd->set_n_backup_active_arena(this, &backup_arena); |
267 | |
268 | if ((rc= table->fill_item_list(&item_list))) |
269 | goto end; |
270 | |
271 | DBUG_ASSERT(send_result_set_metadata.elements == item_list.elements); |
272 | |
273 | /* |
274 | Unless we preserve the original metadata, it will be lost, |
275 | since new fields describe columns of the temporary table. |
276 | Allocate a copy of the name for safety only. Currently |
277 | items with original names are always kept in memory, |
278 | but in case this changes a memory leak may be hard to notice. |
279 | */ |
280 | while ((item_dst= it_dst++, item_org= it_org++)) |
281 | { |
282 | Send_field send_field; |
283 | Item_ident *ident= static_cast<Item_ident *>(item_dst); |
284 | item_org->make_send_field(thd, &send_field); |
285 | |
286 | ident->db_name= thd->strdup(send_field.db_name); |
287 | ident->table_name= thd->strdup(send_field.table_name); |
288 | } |
289 | |
290 | /* |
291 | Original metadata result set should be sent here. After |
292 | mysql_execute_command() is finished, item_list can not be used for |
293 | sending metadata, because it references closed table. |
294 | */ |
295 | rc= result->send_result_set_metadata(item_list, Protocol::SEND_NUM_ROWS); |
296 | |
297 | end: |
298 | thd->restore_active_arena(this, &backup_arena); |
299 | /* Check for thd->is_error() in case of OOM */ |
300 | return rc || thd->is_error(); |
301 | } |
302 | |
303 | |
304 | int Materialized_cursor::open(JOIN *join __attribute__((unused))) |
305 | { |
306 | THD *thd= fake_unit.thd; |
307 | int rc; |
308 | Query_arena backup_arena; |
309 | |
310 | thd->set_n_backup_active_arena(this, &backup_arena); |
311 | |
312 | /* Create a list of fields and start sequential scan. */ |
313 | |
314 | rc= result->prepare(item_list, &fake_unit); |
315 | rc= !rc && table->file->ha_rnd_init_with_error(TRUE); |
316 | is_rnd_inited= !rc; |
317 | |
318 | thd->restore_active_arena(this, &backup_arena); |
319 | |
320 | /* Commit or rollback metadata in the client-server protocol. */ |
321 | |
322 | if (!rc) |
323 | { |
324 | thd->server_status|= SERVER_STATUS_CURSOR_EXISTS; |
325 | result->send_eof(); |
326 | } |
327 | else |
328 | { |
329 | result->abort_result_set(); |
330 | } |
331 | |
332 | return rc; |
333 | } |
334 | |
335 | |
336 | /** |
337 | Fetch up to the given number of rows from a materialized cursor. |
338 | |
339 | Precondition: the cursor is open. |
340 | |
341 | If the cursor points after the last row, the fetch will automatically |
342 | close the cursor and not send any data (except the 'EOF' packet |
343 | with SERVER_STATUS_LAST_ROW_SENT). This is an extra round trip |
344 | and probably should be improved to return |
345 | SERVER_STATUS_LAST_ROW_SENT along with the last row. |
346 | */ |
347 | |
348 | void Materialized_cursor::fetch(ulong num_rows) |
349 | { |
350 | THD *thd= table->in_use; |
351 | |
352 | int res= 0; |
353 | result->begin_dataset(); |
354 | for (fetch_limit+= num_rows; fetch_count < fetch_limit; fetch_count++) |
355 | { |
356 | if ((res= table->file->ha_rnd_next(table->record[0]))) |
357 | break; |
358 | /* Send data only if the read was successful. */ |
359 | /* |
360 | If network write failed (i.e. due to a closed socked), |
361 | the error has already been set. Just return. |
362 | */ |
363 | if (result->send_data(item_list) > 0) |
364 | return; |
365 | } |
366 | |
367 | switch (res) { |
368 | case 0: |
369 | thd->server_status|= SERVER_STATUS_CURSOR_EXISTS; |
370 | result->send_eof(); |
371 | break; |
372 | case HA_ERR_END_OF_FILE: |
373 | thd->server_status|= SERVER_STATUS_LAST_ROW_SENT; |
374 | result->send_eof(); |
375 | close(); |
376 | break; |
377 | default: |
378 | table->file->print_error(res, MYF(0)); |
379 | close(); |
380 | break; |
381 | } |
382 | } |
383 | |
384 | |
385 | void Materialized_cursor::close() |
386 | { |
387 | /* Free item_list items */ |
388 | free_items(); |
389 | if (is_rnd_inited) |
390 | (void) table->file->ha_rnd_end(); |
391 | /* |
392 | We need to grab table->mem_root to prevent free_tmp_table from freeing: |
393 | the cursor object was allocated in this memory. |
394 | */ |
395 | main_mem_root= table->mem_root; |
396 | mem_root= &main_mem_root; |
397 | clear_alloc_root(&table->mem_root); |
398 | free_tmp_table(table->in_use, table); |
399 | table= 0; |
400 | } |
401 | |
402 | |
403 | Materialized_cursor::~Materialized_cursor() |
404 | { |
405 | if (is_open()) |
406 | close(); |
407 | } |
408 | |
409 | |
410 | /* |
411 | @brief |
412 | Perform actions that are to be done when cursor materialization has |
413 | finished. |
414 | |
415 | @detail |
416 | This function is called when "OPEN $cursor" has finished filling the |
417 | temporary table with rows that the cursor will return. |
418 | |
419 | Temporary table has table->field->orig_table pointing at the tables |
420 | that are used in the cursor definition query. Pointers to these tables |
421 | will not be valid after the query finishes. So, we do what is done for |
422 | regular tables: have orig_table point at the table that the fields belong |
423 | to. |
424 | */ |
425 | |
426 | void Materialized_cursor::on_table_fill_finished() |
427 | { |
428 | uint fields= table->s->fields; |
429 | for (uint i= 0; i < fields; i++) |
430 | table->field[i]->orig_table= table->field[i]->table; |
431 | } |
432 | |
433 | /*************************************************************************** |
434 | Select_materialize |
435 | ****************************************************************************/ |
436 | |
437 | bool Select_materialize::send_result_set_metadata(List<Item> &list, uint flags) |
438 | { |
439 | DBUG_ASSERT(table == 0); |
440 | if (create_result_table(unit->thd, unit->get_column_types(true), |
441 | FALSE, |
442 | thd->variables.option_bits | TMP_TABLE_ALL_COLUMNS, |
443 | &empty_clex_str, FALSE, TRUE, TRUE, 0)) |
444 | return TRUE; |
445 | |
446 | materialized_cursor= new (&table->mem_root) |
447 | Materialized_cursor(result, table); |
448 | |
449 | if (!materialized_cursor) |
450 | { |
451 | free_tmp_table(table->in_use, table); |
452 | table= 0; |
453 | return TRUE; |
454 | } |
455 | |
456 | if (materialized_cursor->send_result_set_metadata(unit->thd, list)) |
457 | { |
458 | delete materialized_cursor; |
459 | table= 0; |
460 | materialized_cursor= 0; |
461 | return TRUE; |
462 | } |
463 | |
464 | return FALSE; |
465 | } |
466 | |
467 | |