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