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