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_priv.h"
24 #include "unireg.h"
25 #include "sql_cursor.h"
26 #include "probes_mysql.h"
27 #include "sql_parse.h"                        // mysql_execute_command
28 #include "sql_tmp_table.h"                   // tmp tables
29 #include "debug_sync.h"
30 
31 /****************************************************************************
32   Declarations.
33 ****************************************************************************/
34 
35 /**
36   Materialized_cursor -- an insensitive materialized server-side
37   cursor. The result set of this cursor is saved in a temporary
38   table at open. The cursor itself is simply an interface for the
39   handler of the temporary table.
40 */
41 
42 class Materialized_cursor: public Server_side_cursor
43 {
44   MEM_ROOT main_mem_root;
45   /* A fake unit to supply to select_send when fetching */
46   SELECT_LEX_UNIT fake_unit;
47   TABLE *table;
48   List<Item> item_list;
49   ulong fetch_limit;
50   ulong fetch_count;
51   bool is_rnd_inited;
52 public:
53   Materialized_cursor(select_result *result, TABLE *table);
54 
55   int send_result_set_metadata(THD *thd, List<Item> &send_result_set_metadata);
is_open() const56   virtual bool is_open() const { return table != 0; }
57   virtual int open(JOIN *join MY_ATTRIBUTE((unused)));
58   virtual void fetch(ulong num_rows);
59   virtual void close();
60   virtual ~Materialized_cursor();
61 };
62 
63 
64 /**
65   Select_materialize -- a mediator between a cursor query and the
66   protocol. In case we were not able to open a non-materialzed
67   cursor, it creates an internal temporary HEAP table, and insert
68   all rows into it. When the table reaches max_heap_table_size,
69   it's converted to a MyISAM table. Later this table is used to
70   create a Materialized_cursor.
71 */
72 
73 class Select_materialize: public select_union
74 {
75   select_result *result; /**< the result object of the caller (PS or SP) */
76 public:
77   Materialized_cursor *materialized_cursor;
Select_materialize(select_result * result_arg)78   Select_materialize(select_result *result_arg)
79     :result(result_arg), materialized_cursor(0) {}
80   virtual bool send_result_set_metadata(List<Item> &list, uint flags);
81 };
82 
83 
84 /**************************************************************************/
85 
86 /**
87   Attempt to open a materialized cursor.
88 
89   @param      thd           thread handle
90   @param[in]  result        result class of the caller used as a destination
91                             for the rows fetched from the cursor
92   @param[out] pcursor       a pointer to store a pointer to cursor in
93 
94   @return Error status
95 
96   @retval false -- the query has been successfully executed; in this case
97   pcursor may or may not contain a pointer to an open cursor.
98 
99   @retval true -- an error, 'pcursor' has been left intact.
100 */
101 
mysql_open_cursor(THD * thd,select_result * result,Server_side_cursor ** pcursor)102 bool mysql_open_cursor(THD *thd, select_result *result,
103                        Server_side_cursor **pcursor)
104 {
105   sql_digest_state *parent_digest;
106   PSI_statement_locker *parent_locker;
107   select_result *save_result;
108   Select_materialize *result_materialize;
109   LEX *lex= thd->lex;
110 
111   if (! (result_materialize= new (thd->mem_root) Select_materialize(result)))
112     return true;
113 
114   save_result= lex->result;
115 
116   lex->result= result_materialize;
117 
118   MYSQL_QUERY_EXEC_START(thd->query(),
119                          thd->thread_id,
120                          (char *) (thd->db ? thd->db : ""),
121                          &thd->security_ctx->priv_user[0],
122                          (char *) thd->security_ctx->host_or_ip,
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 select_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(select_result * result_arg,TABLE * table_arg)217 Materialized_cursor::Materialized_cursor(select_result *result_arg,
218                                          TABLE *table_arg)
219   :Server_side_cursor(&table_arg->mem_root, result_arg),
220   table(table_arg),
221   fetch_limit(0),
222   fetch_count(0),
223   is_rnd_inited(0)
224 {
225   fake_unit.init_query();
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->strdup(send_field.db_name);
270     ident->table_name= thd->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 void 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. Just return.
345     */
346     if (result->send_data(item_list))
347       return;
348   }
349 
350   switch (res) {
351   case 0:
352     thd->server_status|= SERVER_STATUS_CURSOR_EXISTS;
353     result->send_eof();
354     break;
355   case HA_ERR_END_OF_FILE:
356     thd->server_status|= SERVER_STATUS_LAST_ROW_SENT;
357     result->send_eof();
358     close();
359     break;
360   default:
361     table->file->print_error(res, MYF(0));
362     close();
363     break;
364   }
365 }
366 
367 
close()368 void Materialized_cursor::close()
369 {
370   /* Free item_list items */
371   free_items();
372   if (is_rnd_inited)
373     (void) table->file->ha_rnd_end();
374   /*
375     We need to grab table->mem_root to prevent free_tmp_table from freeing:
376     the cursor object was allocated in this memory.
377   */
378   main_mem_root= table->mem_root;
379   mem_root= &main_mem_root;
380   clear_alloc_root(&table->mem_root);
381   free_tmp_table(table->in_use, table);
382   table= 0;
383 }
384 
385 
~Materialized_cursor()386 Materialized_cursor::~Materialized_cursor()
387 {
388   if (is_open())
389     close();
390 }
391 
392 
393 /***************************************************************************
394  Select_materialize
395 ****************************************************************************/
396 
send_result_set_metadata(List<Item> & list,uint flags)397 bool Select_materialize::send_result_set_metadata(List<Item> &list, uint flags)
398 {
399   DBUG_ASSERT(table == 0);
400   /*
401     PROCEDURE ANALYSE installs a result filter that has a different set
402     of input and output column Items:
403   */
404   List<Item> *column_types= (unit->first_select()->parent_lex->proc_analyse ?
405                              &list : unit->get_field_list());
406   if (create_result_table(unit->thd, column_types,
407                           FALSE,
408                           thd->variables.option_bits | TMP_TABLE_ALL_COLUMNS,
409                           "", FALSE, TRUE))
410     return TRUE;
411 
412   materialized_cursor= new (&table->mem_root)
413                        Materialized_cursor(result, table);
414 
415   if (!materialized_cursor)
416   {
417     free_tmp_table(table->in_use, table);
418     table= 0;
419     return TRUE;
420   }
421 
422   if (materialized_cursor->send_result_set_metadata(unit->thd, list))
423   {
424     delete materialized_cursor;
425     table= 0;
426     materialized_cursor= 0;
427     return TRUE;
428   }
429 
430   /*
431     close_thread_tables() will be called in mysql_execute_command() which
432     will close all tables except the cursor temporary table. Hence set the
433     orig_table in the field definition to NULL.
434   */
435   for (Field **fld= this->table->field; *fld; fld++)
436      (*fld)->orig_table= NULL;
437 
438   return FALSE;
439 }
440 
441