1 /* Copyright (c) 2000, 2013, 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 
24 /* Write some debug info */
25 
26 
27 #include "sql_priv.h"
28 #include "unireg.h"
29 #include "sql_test.h"
30 #include "sql_base.h" // table_def_cache, table_cache_count, unused_tables
31 #include "sql_show.h" // calc_sum_of_all_status
32 #include "sql_select.h"
33 #include "opt_trace.h"
34 #include "keycaches.h"
35 #include "sql_optimizer.h"  // JOIN
36 #include "opt_explain.h"    // join_type_str
37 #include <hash.h>
38 #include <thr_alarm.h>
39 #if defined(HAVE_MALLOC_INFO) && defined(HAVE_MALLOC_H)
40 #include <malloc.h>
41 #elif defined(HAVE_MALLOC_INFO) && defined(HAVE_SYS_MALLOC_H)
42 #include <sys/malloc.h>
43 #endif
44 
45 #ifdef HAVE_EVENT_SCHEDULER
46 #include "events.h"
47 #endif
48 
49 #include "global_threads.h"
50 #include "table_cache.h" // table_cache_manager
51 
52 const char *lock_descriptions[TL_WRITE_ONLY + 1] =
53 {
54   /* TL_UNLOCK                  */  "No lock",
55   /* TL_READ_DEFAULT            */  NULL,
56   /* TL_READ                    */  "Low priority read lock",
57   /* TL_READ_WITH_SHARED_LOCKS  */  "Shared read lock",
58   /* TL_READ_HIGH_PRIORITY      */  "High priority read lock",
59   /* TL_READ_NO_INSERT          */  "Read lock without concurrent inserts",
60   /* TL_WRITE_ALLOW_WRITE       */  "Write lock that allows other writers",
61   /* TL_WRITE_CONCURRENT_INSERT */  "Concurrent insert lock",
62   /* TL_WRITE_DELAYED           */  "Lock used by delayed insert",
63   /* TL_WRITE_DEFAULT           */  NULL,
64   /* TL_WRITE_LOW_PRIORITY      */  "Low priority write lock",
65   /* TL_WRITE                   */  "High priority write lock",
66   /* TL_WRITE_ONLY              */  "Highest priority write lock"
67 };
68 
69 
70 #ifndef DBUG_OFF
71 
72 void
print_where(Item * cond,const char * info,enum_query_type query_type)73 print_where(Item *cond,const char *info, enum_query_type query_type)
74 {
75   char buff[256];
76   String str(buff,(uint32) sizeof(buff), system_charset_info);
77   str.length(0);
78   if (cond)
79     cond->print(&str, query_type);
80   str.append('\0');
81 
82   DBUG_LOCK_FILE;
83   (void) fprintf(DBUG_FILE,"\nWHERE:(%s) %p ", info, cond);
84   (void) fputs(str.ptr(),DBUG_FILE);
85   (void) fputc('\n',DBUG_FILE);
86   DBUG_UNLOCK_FILE;
87 }
88 	/* This is for debugging purposes */
89 
90 
print_cached_tables(void)91 static void print_cached_tables(void)
92 {
93   /* purecov: begin tested */
94   table_cache_manager.lock_all_and_tdc();
95 
96   table_cache_manager.print_tables();
97 
98   printf("\nCurrent refresh version: %ld\n",refresh_version);
99   if (my_hash_check(&table_def_cache))
100     printf("Error: Table definition hash table is corrupted\n");
101   fflush(stdout);
102   table_cache_manager.unlock_all_and_tdc();
103   /* purecov: end */
104   return;
105 }
106 
107 
108 void
TEST_join(JOIN * join)109 TEST_join(JOIN *join)
110 {
111   uint i,ref;
112   DBUG_ENTER("TEST_join");
113 
114   /*
115     Assemble results of all the calls to full_name() first,
116     in order not to garble the tabular output below.
117   */
118   String ref_key_parts[MAX_TABLES];
119   for (i= 0; i < join->tables; i++)
120   {
121     JOIN_TAB *tab= join->join_tab + i;
122     for (ref= 0; ref < tab->ref.key_parts; ref++)
123     {
124       ref_key_parts[i].append(tab->ref.items[ref]->full_name());
125       ref_key_parts[i].append("  ");
126     }
127   }
128 
129   DBUG_LOCK_FILE;
130   (void) fputs("\nInfo about JOIN\n",DBUG_FILE);
131   for (i=0 ; i < join->tables ; i++)
132   {
133     JOIN_TAB *tab=join->join_tab+i;
134     TABLE *form=tab->table;
135     if (!form)
136       continue;
137     char key_map_buff[128];
138     fprintf(DBUG_FILE,"%-16.16s  type: %-7s  q_keys: %s  refs: %d  key: %d  len: %d\n",
139 	    form->alias,
140 	    join_type_str[tab->type],
141 	    tab->keys.print(key_map_buff),
142 	    tab->ref.key_parts,
143 	    tab->ref.key,
144 	    tab->ref.key_length);
145     if (tab->select)
146     {
147       char buf[MAX_KEY/8+1];
148       if (tab->use_quick == QS_DYNAMIC_RANGE)
149 	fprintf(DBUG_FILE,
150 		"                  quick select checked for each record (keys: %s)\n",
151 		tab->select->quick_keys.print(buf));
152       else if (tab->select->quick)
153       {
154 	fprintf(DBUG_FILE, "                  quick select used:\n");
155         tab->select->quick->dbug_dump(18, FALSE);
156       }
157       else
158 	(void) fputs("                  select used\n",DBUG_FILE);
159     }
160     if (tab->ref.key_parts)
161     {
162       fprintf(DBUG_FILE,
163               "                  refs:  %s\n", ref_key_parts[i].ptr());
164     }
165   }
166   DBUG_UNLOCK_FILE;
167   DBUG_VOID_RETURN;
168 }
169 
170 #endif /* !DBUG_OFF */
171 
print_keyuse_array(Opt_trace_context * trace,const Key_use_array * keyuse_array)172 void print_keyuse_array(Opt_trace_context *trace,
173                         const Key_use_array *keyuse_array)
174 {
175 #if !defined(DBUG_OFF) || defined(OPTIMIZER_TRACE)
176   if (unlikely(!trace->is_started()))
177     return;
178   Opt_trace_object wrapper(trace);
179   Opt_trace_array  trace_key_uses(trace, "ref_optimizer_key_uses");
180   DBUG_PRINT("opt", ("Key_use array (%zu elements)", keyuse_array->size()));
181   for (uint i= 0; i < keyuse_array->size(); i++)
182   {
183     const Key_use &keyuse= keyuse_array->at(i);
184      // those are too obscure for opt trace
185     DBUG_PRINT("opt", ("Key_use: optimize= %d used_tables=0x%llx "
186                        "ref_table_rows= %lu keypart_map= %0lx",
187                        keyuse.optimize, keyuse.used_tables,
188                        (ulong)keyuse.ref_table_rows, keyuse.keypart_map));
189     Opt_trace_object(trace).
190       add_utf8_table(keyuse.table).
191       add_utf8("field", (keyuse.keypart == FT_KEYPART) ? "<fulltext>" :
192                keyuse.table->key_info[keyuse.key].
193                key_part[keyuse.keypart].field->field_name).
194       add("equals", keyuse.val).
195       add("null_rejecting", keyuse.null_rejecting);
196   }
197 #endif /* !DBUG_OFF || OPTIMIZER_TRACE */
198 }
199 
200 #ifndef DBUG_OFF
201 /* purecov: begin inspected */
202 
203 /*
204   Print the current state during query optimization.
205 
206   SYNOPSIS
207     print_plan()
208     join         pointer to the structure providing all context info for
209                  the query
210     read_time    the cost of the best partial plan
211     record_count estimate for the number of records returned by the best
212                  partial plan
213     idx          length of the partial QEP in 'join->positions';
214                  also an index in the array 'join->best_ref';
215     info         comment string to appear above the printout
216 
217   DESCRIPTION
218     This function prints to the log file DBUG_FILE the members of 'join' that
219     are used during query optimization (join->positions, join->best_positions,
220     and join->best_ref) and few other related variables (read_time,
221     record_count).
222     Useful to trace query optimizer functions.
223 
224   RETURN
225     None
226 */
227 
228 void
print_plan(JOIN * join,uint idx,double record_count,double read_time,double current_read_time,const char * info)229 print_plan(JOIN* join, uint idx, double record_count, double read_time,
230            double current_read_time, const char *info)
231 {
232   uint i;
233   POSITION pos;
234   JOIN_TAB *join_table;
235   JOIN_TAB **plan_nodes;
236   TABLE*   table;
237 
238   if (info == 0)
239     info= "";
240 
241   DBUG_LOCK_FILE;
242   if (join->best_read == DBL_MAX)
243   {
244     fprintf(DBUG_FILE,
245             "%s; idx: %u  best: DBL_MAX  atime: %g  itime: %g  count: %g\n",
246             info, idx, current_read_time, read_time, record_count);
247   }
248   else
249   {
250     fprintf(DBUG_FILE,
251             "%s; idx :%u  best: %g  accumulated: %g  increment: %g  count: %g\n",
252             info, idx, join->best_read, current_read_time, read_time,
253             record_count);
254   }
255 
256   /* Print the tables in JOIN->positions */
257   fputs("     POSITIONS: ", DBUG_FILE);
258   for (i= 0; i < idx ; i++)
259   {
260     pos = join->positions[i];
261     table= pos.table->table;
262     if (table)
263       fputs(table->alias, DBUG_FILE);
264     fputc(' ', DBUG_FILE);
265   }
266   fputc('\n', DBUG_FILE);
267 
268   /*
269     Print the tables in JOIN->best_positions only if at least one complete plan
270     has been found. An indicator for this is the value of 'join->best_read'.
271   */
272   if (join->best_read < DBL_MAX)
273   {
274     fputs("BEST_POSITIONS: ", DBUG_FILE);
275     for (i= 0; i < idx ; i++)
276     {
277       pos= join->best_positions[i];
278       table= pos.table->table;
279       if (table)
280         fputs(table->alias, DBUG_FILE);
281       fputc(' ', DBUG_FILE);
282     }
283   }
284   fputc('\n', DBUG_FILE);
285 
286   /* Print the tables in JOIN->best_ref */
287   fputs("      BEST_REF: ", DBUG_FILE);
288   for (plan_nodes= join->best_ref ; *plan_nodes ; plan_nodes++)
289   {
290     join_table= (*plan_nodes);
291     fputs(join_table->table->s->table_name.str, DBUG_FILE);
292     fprintf(DBUG_FILE, "(%lu,%lu,%lu)",
293             (ulong) join_table->found_records,
294             (ulong) join_table->records,
295             (ulong) join_table->read_time);
296     fputc(' ', DBUG_FILE);
297   }
298   fputc('\n', DBUG_FILE);
299 
300   DBUG_UNLOCK_FILE;
301 }
302 
303 #endif  /* !DBUG_OFF */
304 
305 C_MODE_START
306 static int dl_compare(const void *p1, const void *p2);
307 static int print_key_cache_status(const char *name, KEY_CACHE *key_cache);
308 C_MODE_END
309 
310 typedef struct st_debug_lock
311 {
312   ulong thread_id;
313   char table_name[FN_REFLEN];
314   bool waiting;
315   const char *lock_text;
316   enum thr_lock_type type;
317 } TABLE_LOCK_INFO;
318 
dl_compare(const void * p1,const void * p2)319 static int dl_compare(const void *p1, const void *p2)
320 {
321   TABLE_LOCK_INFO *a, *b;
322 
323   a= (TABLE_LOCK_INFO *) p1;
324   b= (TABLE_LOCK_INFO *) p2;
325 
326   if (a->thread_id > b->thread_id)
327     return 1;
328   if (a->thread_id < b->thread_id)
329     return -1;
330   if (a->waiting == b->waiting)
331     return 0;
332   else if (a->waiting)
333     return -1;
334   return 1;
335 }
336 
337 
push_locks_into_array(DYNAMIC_ARRAY * ar,THR_LOCK_DATA * data,bool wait,const char * text)338 static void push_locks_into_array(DYNAMIC_ARRAY *ar, THR_LOCK_DATA *data,
339 				  bool wait, const char *text)
340 {
341   if (data)
342   {
343     TABLE *table=(TABLE *)data->debug_print_param;
344     if (table && table->s->tmp_table == NO_TMP_TABLE)
345     {
346       TABLE_LOCK_INFO table_lock_info;
347       table_lock_info.thread_id= table->in_use->thread_id;
348       memcpy(table_lock_info.table_name, table->s->table_cache_key.str,
349 	     table->s->table_cache_key.length);
350       table_lock_info.table_name[strlen(table_lock_info.table_name)]='.';
351       table_lock_info.waiting=wait;
352       table_lock_info.lock_text=text;
353       // lock_type is also obtainable from THR_LOCK_DATA
354       table_lock_info.type=table->reginfo.lock_type;
355       (void) push_dynamic(ar,(uchar*) &table_lock_info);
356     }
357   }
358 }
359 
360 
361 /*
362   Regarding MERGE tables:
363 
364   For now, the best option is to use the common TABLE *pointer for all
365   cases;  The drawback is that for MERGE tables we will see many locks
366   for the merge tables even if some of them are for individual tables.
367 
368   The way to solve this is to add to 'THR_LOCK' structure a pointer to
369   the filename and use this when printing the data.
370   (We can for now ignore this and just print the same name for all merge
371   table parts;  Please add the above as a comment to the display_lock
372   function so that we can easily add this if we ever need this.
373 */
374 
display_table_locks(void)375 static void display_table_locks(void)
376 {
377   LIST *list;
378   void *saved_base;
379   DYNAMIC_ARRAY saved_table_locks;
380 
381   (void) my_init_dynamic_array(&saved_table_locks,sizeof(TABLE_LOCK_INFO),
382                                table_cache_manager.cached_tables() + 20,50);
383   mysql_mutex_lock(&THR_LOCK_lock);
384   for (list= thr_lock_thread_list; list; list= list_rest(list))
385   {
386     THR_LOCK *lock=(THR_LOCK*) list->data;
387 
388     mysql_mutex_lock(&lock->mutex);
389     push_locks_into_array(&saved_table_locks, lock->write.data, FALSE,
390 			  "Locked - write");
391     push_locks_into_array(&saved_table_locks, lock->write_wait.data, TRUE,
392 			  "Waiting - write");
393     push_locks_into_array(&saved_table_locks, lock->read.data, FALSE,
394 			  "Locked - read");
395     push_locks_into_array(&saved_table_locks, lock->read_wait.data, TRUE,
396 			  "Waiting - read");
397     mysql_mutex_unlock(&lock->mutex);
398   }
399   mysql_mutex_unlock(&THR_LOCK_lock);
400 
401   if (!saved_table_locks.elements)
402     goto end;
403 
404   saved_base= dynamic_element(&saved_table_locks, 0, TABLE_LOCK_INFO *);
405   my_qsort(saved_base, saved_table_locks.elements, sizeof(TABLE_LOCK_INFO),
406            dl_compare);
407   freeze_size(&saved_table_locks);
408 
409   puts("\nThread database.table_name          Locked/Waiting        Lock_type\n");
410 
411   unsigned int i;
412   for (i=0 ; i < saved_table_locks.elements ; i++)
413   {
414     TABLE_LOCK_INFO *dl_ptr=dynamic_element(&saved_table_locks,i,TABLE_LOCK_INFO*);
415     printf("%-8ld%-28.28s%-22s%s\n",
416 	   dl_ptr->thread_id,dl_ptr->table_name,dl_ptr->lock_text,lock_descriptions[(int)dl_ptr->type]);
417   }
418   puts("\n\n");
419 end:
420   delete_dynamic(&saved_table_locks);
421 }
422 
423 
print_key_cache_status(const char * name,KEY_CACHE * key_cache)424 static int print_key_cache_status(const char *name, KEY_CACHE *key_cache)
425 {
426   char llbuff1[22];
427   char llbuff2[22];
428   char llbuff3[22];
429   char llbuff4[22];
430 
431   if (!key_cache->key_cache_inited)
432   {
433     printf("%s: Not in use\n", name);
434   }
435   else
436   {
437     printf("%s\n\
438 Buffer_size:    %10lu\n\
439 Block_size:     %10lu\n\
440 Division_limit: %10lu\n\
441 Age_limit:      %10lu\n\
442 blocks used:    %10lu\n\
443 not flushed:    %10lu\n\
444 w_requests:     %10s\n\
445 writes:         %10s\n\
446 r_requests:     %10s\n\
447 reads:          %10s\n\n",
448 	   name,
449 	   (ulong) key_cache->param_buff_size,
450            (ulong)key_cache->param_block_size,
451 	   (ulong)key_cache->param_division_limit,
452            (ulong)key_cache->param_age_threshold,
453 	   key_cache->blocks_used,key_cache->global_blocks_changed,
454 	   llstr(key_cache->global_cache_w_requests,llbuff1),
455            llstr(key_cache->global_cache_write,llbuff2),
456 	   llstr(key_cache->global_cache_r_requests,llbuff3),
457            llstr(key_cache->global_cache_read,llbuff4));
458   }
459   return 0;
460 }
461 
462 
mysql_print_status()463 void mysql_print_status()
464 {
465   char current_dir[FN_REFLEN];
466   STATUS_VAR tmp;
467 
468   calc_sum_of_all_status(&tmp);
469   printf("\nStatus information:\n\n");
470   (void) my_getwd(current_dir, sizeof(current_dir),MYF(0));
471   printf("Current dir: %s\n", current_dir);
472   printf("Running threads: %u  Stack size: %ld\n", get_thread_count(),
473 	 (long) my_thread_stack_size);
474   thr_print_locks();				// Write some debug info
475 #ifndef DBUG_OFF
476   print_cached_tables();
477 #endif
478   /* Print key cache status */
479   puts("\nKey caches:");
480   process_key_caches(print_key_cache_status);
481   mysql_mutex_lock(&LOCK_status);
482   printf("\nhandler status:\n\
483 read_key:   %10llu\n\
484 read_next:  %10llu\n\
485 read_rnd    %10llu\n\
486 read_first: %10llu\n\
487 write:      %10llu\n\
488 delete      %10llu\n\
489 update:     %10llu\n",
490 	 tmp.ha_read_key_count,
491 	 tmp.ha_read_next_count,
492 	 tmp.ha_read_rnd_count,
493 	 tmp.ha_read_first_count,
494 	 tmp.ha_write_count,
495 	 tmp.ha_delete_count,
496 	 tmp.ha_update_count);
497   mysql_mutex_unlock(&LOCK_status);
498   printf("\nTable status:\n\
499 Opened tables: %10lu\n\
500 Open tables:   %10lu\n\
501 Open files:    %10lu\n\
502 Open streams:  %10lu\n",
503 	 (ulong) tmp.opened_tables,
504 	 (ulong) table_cache_manager.cached_tables(),
505 	 (ulong) my_file_opened,
506 	 (ulong) my_stream_opened);
507 
508   ALARM_INFO alarm_info;
509 #ifndef DONT_USE_THR_ALARM
510   thr_alarm_info(&alarm_info);
511   printf("\nAlarm status:\n\
512 Active alarms:   %u\n\
513 Max used alarms: %u\n\
514 Next alarm time: %lu\n",
515 	 alarm_info.active_alarms,
516 	 alarm_info.max_used_alarms,
517 	 alarm_info.next_alarm_time);
518 #endif
519   display_table_locks();
520 #ifdef HAVE_MALLOC_INFO
521   printf("\nMemory status:\n");
522   malloc_info(0, stdout);
523 #endif
524 
525 #ifdef HAVE_EVENT_SCHEDULER
526   Events::dump_internal_status();
527 #endif
528   puts("");
529   fflush(stdout);
530 }
531 
532 
533 #ifndef DBUG_OFF
534 #ifdef EXTRA_DEBUG_DUMP_TABLE_LISTS
535 
536 
537 /*
538   A fixed-size FIFO pointer queue that also doesn't allow one to put an
539   element that has previously been put into it.
540 
541   There is a hard-coded limit of the total number of queue put operations.
542   The implementation is trivial and is intended for use in debug dumps only.
543 */
544 
545 template <class T> class Unique_fifo_queue
546 {
547 public:
548   /* Add an element to the queue */
push_back(T * tbl)549   void push_back(T *tbl)
550   {
551     if (!tbl)
552       return;
553     // check if we've already scheduled and/or dumped the element
554     for (int i= 0; i < last; i++)
555     {
556       if (elems[i] == tbl)
557         return;
558     }
559     elems[last++]=  tbl;
560   }
561 
pop_first(T ** elem)562   bool pop_first(T **elem)
563   {
564     if (first < last)
565     {
566       *elem= elems[first++];
567       return TRUE;
568     }
569     return FALSE;
570   }
571 
reset()572   void reset()
573   {
574     first= last= 0;
575   }
576   enum { MAX_ELEMS=1000};
577   T *elems[MAX_ELEMS];
578   int first; // First undumped table
579   int last;  // Last undumped element
580 };
581 
582 class Dbug_table_list_dumper
583 {
584   FILE *out;
585   Unique_fifo_queue<TABLE_LIST> tables_fifo;
586   Unique_fifo_queue<List<TABLE_LIST> > tbl_lists;
587 public:
588   void dump_one_struct(TABLE_LIST *tbl);
589 
590   int dump_graph(st_select_lex *select_lex, TABLE_LIST *first_leaf);
591 };
592 
593 
dump_TABLE_LIST_graph(SELECT_LEX * select_lex,TABLE_LIST * tl)594 void dump_TABLE_LIST_graph(SELECT_LEX *select_lex, TABLE_LIST* tl)
595 {
596   Dbug_table_list_dumper dumper;
597   dumper.dump_graph(select_lex, tl);
598 }
599 
600 
601 /*
602   - Dump one TABLE_LIST objects and its outgoing edges
603   - Schedule that other objects seen along the edges are dumped too.
604 */
605 
dump_one_struct(TABLE_LIST * tbl)606 void Dbug_table_list_dumper::dump_one_struct(TABLE_LIST *tbl)
607 {
608   fprintf(out, "\"%p\" [\n", tbl);
609   fprintf(out, "  label = \"%p|", tbl);
610   fprintf(out, "alias=%s|", tbl->alias? tbl->alias : "NULL");
611   fprintf(out, "<next_leaf>next_leaf=%p|", tbl->next_leaf);
612   fprintf(out, "<next_local>next_local=%p|", tbl->next_local);
613   fprintf(out, "<next_global>next_global=%p|", tbl->next_global);
614   fprintf(out, "<embedding>embedding=%p", tbl->embedding);
615 
616   if (tbl->nested_join)
617     fprintf(out, "|<nested_j>nested_j=%p", tbl->nested_join);
618   if (tbl->join_list)
619     fprintf(out, "|<join_list>join_list=%p", tbl->join_list);
620   if (tbl->on_expr)
621     fprintf(out, "|<on_expr>on_expr=%p", tbl->on_expr);
622   fprintf(out, "\"\n");
623   fprintf(out, "  shape = \"record\"\n];\n\n");
624 
625   if (tbl->next_leaf)
626   {
627     fprintf(out, "\n\"%p\":next_leaf -> \"%p\"[ color = \"#000000\" ];\n",
628             tbl, tbl->next_leaf);
629     tables_fifo.push_back(tbl->next_leaf);
630   }
631   if (tbl->next_local)
632   {
633     fprintf(out, "\n\"%p\":next_local -> \"%p\"[ color = \"#404040\" ];\n",
634             tbl, tbl->next_local);
635     tables_fifo.push_back(tbl->next_local);
636   }
637   if (tbl->next_global)
638   {
639     fprintf(out, "\n\"%p\":next_global -> \"%p\"[ color = \"#808080\" ];\n",
640             tbl, tbl->next_global);
641     tables_fifo.push_back(tbl->next_global);
642   }
643 
644   if (tbl->embedding)
645   {
646     fprintf(out, "\n\"%p\":embedding -> \"%p\"[ color = \"#FF0000\" ];\n",
647             tbl, tbl->embedding);
648     tables_fifo.push_back(tbl->embedding);
649   }
650 
651   if (tbl->join_list)
652   {
653     fprintf(out, "\n\"%p\":join_list -> \"%p\"[ color = \"#0000FF\" ];\n",
654             tbl, tbl->join_list);
655     tbl_lists.push_back(tbl->join_list);
656   }
657 }
658 
659 
dump_graph(st_select_lex * select_lex,TABLE_LIST * first_leaf)660 int Dbug_table_list_dumper::dump_graph(st_select_lex *select_lex,
661                                        TABLE_LIST *first_leaf)
662 {
663   DBUG_ENTER("Dbug_table_list_dumper::dump_graph");
664   char filename[500];
665   int no = 0;
666   do
667   {
668     sprintf(filename, "tlist_tree%.3d.g", no);
669     if ((out= fopen(filename, "rt")))
670     {
671       /* File exists, try next name */
672       fclose(out);
673     }
674     no++;
675   } while (out);
676 
677   /* Ok, found an unoccupied name, create the file */
678   if (!(out= fopen(filename, "wt")))
679   {
680     DBUG_PRINT("tree_dump", ("Failed to create output file"));
681     DBUG_RETURN(1);
682   }
683 
684   DBUG_PRINT("tree_dump", ("dumping tree to %s", filename));
685 
686   fputs("digraph g {\n", out);
687   fputs("graph [", out);
688   fputs("  rankdir = \"LR\"", out);
689   fputs("];", out);
690 
691   TABLE_LIST *tbl;
692   tables_fifo.reset();
693   dump_one_struct(first_leaf);
694   while (tables_fifo.pop_first(&tbl))
695   {
696     dump_one_struct(tbl);
697   }
698 
699   List<TABLE_LIST> *plist;
700   tbl_lists.push_back(&select_lex->top_join_list);
701   while (tbl_lists.pop_first(&plist))
702   {
703     fprintf(out, "\"%p\" [\n", plist);
704     fprintf(out, "  bgcolor = \"\"");
705     fprintf(out, "  label = \"L %p\"", plist);
706     fprintf(out, "  shape = \"record\"\n];\n\n");
707   }
708 
709   fprintf(out, " { rank = same; ");
710   for (TABLE_LIST *tl=first_leaf; tl; tl= tl->next_leaf)
711     fprintf(out, " \"%p\"; ", tl);
712   fprintf(out, "};\n");
713   fputs("}", out);
714   fclose(out);
715 
716   char filename2[500];
717   filename[strlen(filename) - 1]= 0;
718   filename[strlen(filename) - 1]= 0;
719   sprintf(filename2, "%s.query", filename);
720 
721   if ((out= fopen(filename2, "wt")))
722   {
723 //    fprintf(out, "%s", current_thd->query);
724     fclose(out);
725   }
726   DBUG_RETURN(0);
727 }
728 
729 #endif
730 
731 #endif
732 
733