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(¤t_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