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