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
38class 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;
48public:
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
75class Select_materialize: public select_unit
76{
77 select_result *result; /**< the result object of the caller (PS or SP) */
78public:
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
116int 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
198end:
199 delete result_materialize;
200 return rc;
201}
202
203/****************************************************************************
204 Server_side_cursor
205****************************************************************************/
206
207Server_side_cursor::~Server_side_cursor()
208{
209}
210
211
212void 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
234Materialized_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
256int 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
297end:
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
304int 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
348void 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
385void 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
403Materialized_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
426void 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
437bool 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