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