~ubuntu-branches/ubuntu/utopic/mysql-workbench/utopic

« back to all changes in this revision

Viewing changes to plugins/wb.query.analysis/explain_renderer.py

  • Committer: Package Import Robot
  • Author(s): Dmitry Smirnov
  • Date: 2014-07-03 23:41:54 UTC
  • mfrom: (1.2.5)
  • Revision ID: package-import@ubuntu.com-20140703234154-4cjcmweb9f0kzbqf
Tags: 6.1.7+dfsg-1
* New upstream release [June 2014].
* Added "debian/gbp.conf".

Show diffs side-by-side

added added

removed removed

Lines of Context:
23
23
 
24
24
from workbench.graphics.canvas import VBoxFigure, Canvas, DiamondShapeFigure, RectangleShapeFigure, TextFigure, HFill, draw_varrow, draw_harrow
25
25
from workbench.graphics.cairo_utils import ImageSurface, Context
 
26
import cairo
26
27
 
27
28
import StringIO
28
29
import json
32
33
    fp = StringIO.StringIO(text)
33
34
    return json.load(fp)
34
35
 
 
36
def reformat_expression(expression):
 
37
    # turn the expression into a valid SQL query
 
38
    sql = "SELECT " + expression
 
39
    try:
 
40
        import sqlide_grt
 
41
        sql = sqlide_grt.reformatSQLStatement(sql)
 
42
        # strip the artificially added SELECT
 
43
        expression = sql.partition(" ")[-1].strip()
 
44
    except ImportError:
 
45
        log_error("Can't import sqlide_grt, disabling expression reformatting\n")
 
46
    return expression
35
47
 
36
48
 
37
49
def fmt_number(c):
74
86
    arrow_w = 3
75
87
    arrow_h = 7
76
88
    is_operation = False
 
89
    is_container = False
77
90
    
78
91
    def __init__(self, context):
79
92
        VBoxFigure.__init__(self)
133
146
    def hconnect_pos_offset(self):
134
147
        return self._figure.y + self._figure.height/2
135
148
 
 
149
    def get_read_eval_cost(self):
 
150
        return None
136
151
 
137
152
    @property
138
153
    def cost_value(self):
139
 
        name = self._context.displayed_cost_info
140
 
        if self.cost_info and name in self.cost_info:
141
 
            cost_value = self.cost_info.get(name)
 
154
        cost_value = None
 
155
        if self._context.displayed_cost_info == "read_eval_cost":
 
156
            # 5.7 server only
 
157
            # for table nodes, we display the read+eval value
 
158
            # for nested_loop nodes, we display prefix_cost
 
159
            cost_value = self.get_read_eval_cost()
 
160
 
 
161
        if cost_value is not None:
142
162
            try:
143
163
                return float(cost_value)
144
164
            except ValueError: # some values have a unit suffix
275
295
            self._figure.move(child_align_x - self.inner_width/2, self._figure.y)
276
296
 
277
297
 
 
298
    def draw_varrow(self, cr, x, y1, y2):
 
299
        cr.set_line_width(self.get_line_width())
 
300
        cr.move_to(x, y1)
 
301
        cr.line_to(x, y2 + 6 + self.get_line_width())
 
302
        cr.stroke()
 
303
        draw_varrow(cr, (x, y2), 10+self.get_line_width(), 6+self.get_line_width())
 
304
        cr.fill()
 
305
 
 
306
 
 
307
    def draw_harrow(self, cr, x1, x2, y, w=10, h=6):
 
308
        cr.set_line_width(self.get_line_width())
 
309
        cr.move_to(x1, y)
 
310
        cr.line_to(x2 - (w + self.get_line_width()), y)
 
311
        cr.stroke()
 
312
        draw_harrow(cr, (x2, y), w+self.get_line_width(), h+self.get_line_width())
 
313
        cr.fill()
 
314
 
 
315
 
278
316
    # hover tip handling
279
317
    def handle_hover_out(self, fig, x, y):
280
318
        if self._context.tooltip:
295
333
        if self._context.tooltip:
296
334
            self._context.tooltip.close()
297
335
            self._context.tooltip = None
298
 
        
 
336
 
 
337
        if self._context.overview_mode:
 
338
            return
 
339
 
299
340
        text = self.get_hint_text()
300
341
        if text:
301
342
            if type(text) is unicode:
302
343
                text = text.encode("utf8")
303
344
            self._context.tooltip = mforms.newPopover(mforms.PopoverStyleTooltip)
304
345
 
305
 
            xx, yy = self._context.client_to_screen(fig._figure.root_x + fig.inner_width, fig._figure.root_y + fig.inner_height/2)
 
346
            if fig.is_container:
 
347
                xx, yy = self._context.client_to_screen(x + 10, y)
 
348
            else:
 
349
                xx, yy = self._context.client_to_screen(fig._figure.root_x + fig.inner_width, fig._figure.root_y + fig.inner_height/2)
306
350
            box = mforms.newBox(False)
307
351
            box.set_spacing(0)
308
352
            t = ""
328
372
            self._context.tooltip.set_size(max(box.get_width(), 100), max(box.get_height(), 50))
329
373
        
330
374
            self._context.tooltip.set_content(box)
331
 
            self._context.tooltip.show(xx, yy, mforms.Right)
 
375
            self._context.tooltip.add_close_callback(self._context.close_tooltip)
 
376
            self._context.tooltip.show_and_track(self._context._view, xx, yy, mforms.Right)
332
377
 
333
378
 
334
379
 
376
421
    def vconnect_pos_offset(self):
377
422
        return self.child_aside.width + self._context.hspacing + self.child_below.inner_width/2
378
423
 
379
 
 
380
424
    @property
381
425
    def rows_count(self):
382
 
        return self.child_below.rows_produced
 
426
        if self._context.server_version.is_supported_mysql_version_at_least(5, 7):
 
427
            return self.child_below.rows_produced
 
428
        return None
 
429
 
 
430
 
 
431
    def get_read_eval_cost(self):
 
432
        return float(self.child_below.cost_info.get("prefix_cost", 0))
383
433
 
384
434
 
385
435
    def __repr__(self):
392
442
 
393
443
            cr.save()
394
444
            cr.set_source_rgba(0, 0, 0, 1)
395
 
            cr.set_line_width(self.get_line_width())
396
445
            if isinstance(self.parent, NestedLoopNode):
397
446
                if not is_inside_a_box:
398
 
                    cr.move_to(self.harrow_source[0], self.parent.harrow_target[1])
399
 
                    cr.line_to(*self.parent.harrow_target)
400
 
                    cr.stroke()
401
 
                    draw_harrow(cr, self.parent.harrow_target, 10, 6)
402
 
                    cr.fill()
 
447
                    self.draw_harrow(cr, self.harrow_source[0], self.parent.harrow_target[0], self.parent.harrow_target[1])
403
448
                self.render_row_count(cr, self.harrow_source[0] + 4, self.harrow_source[1] - 8)
404
449
            else:
405
450
                if not is_inside_a_box:
406
 
                    cr.move_to(*self.varrow_source)
407
 
                    cr.line_to(self.varrow_source[0], self.parent.varrow_target[1])
408
 
                    cr.stroke()
409
 
                    draw_varrow(cr, (self.varrow_source[0], self.parent.varrow_target[1]), 10, 6)
410
 
                    cr.fill()
 
451
                    self.draw_varrow(cr, self.varrow_source[0], self.varrow_source[1], self.parent.varrow_target[1])
411
452
                self.render_row_count(cr, self.varrow_source[0] + 4, self.varrow_source[1])
 
453
            self.render_cost(cr, self._figure.root_x, self.varrow_source[1] - 5)
412
454
            cr.restore()
413
455
 
414
456
 
421
463
        self._figure.do_relayout(ctx)
422
464
        below.do_relayout(ctx)
423
465
        aside.do_relayout(ctx)
424
 
 
425
466
        total_width = below.width + aside.width + self._context.hspacing
426
467
        self._width = max(self.inner_width, total_width)
427
468
        # layout other nested loop nodes horizontally
439
480
        below.move(aside.width + self._context.hspacing, self._figure.height + self._context.vspacing)
440
481
 
441
482
 
 
483
    def get_hint_text(self):
 
484
        text = """*%(join_buffer)s
 
485
 
 
486
""" % {"join_buffer" : self.join_buffer}
 
487
        if self.child_below.cost_info:
 
488
            text += "Prefix Cost: %(prefix_cost)s" % self.child_below.cost_info # 5.7 only
 
489
 
 
490
        return text
 
491
 
442
492
 
443
493
BLUE = (0.25, 0.5, 0.75, 1)
444
494
GREEN = (0.0, 0.5, 0.0, 1)
464
514
                      ("index",           RED, "Full Index Scan", "High - especially for large indexes"),
465
515
                      ("ALL",             RED, "Full Table Scan", """Very High - very costly for large tables (not so much for small ones).
466
516
No usable indexes were found for the table and the optimizer must search every row.
467
 
Consider adding an index."""),
 
517
This could also mean the search range is so broad that the index would be useless."""),
468
518
                      ("UNKNOWN",         BLACK, "unknown", ""), # the default, in case none matches
469
519
                      ]
470
520
    
528
578
            return [self.child_attached_subqueries]
529
579
        return []
530
580
 
531
 
    
 
581
    def get_read_eval_cost(self):
 
582
        if self.cost_info:
 
583
            return float(self.cost_info.get("read_cost", 0)) + float(self.cost_info.get("eval_cost", 0))
 
584
        return None
 
585
 
532
586
    @property
533
587
    def rows_count(self):
534
588
        return self.rows_examined
581
635
        if "used_columns" in self.info or "used_key_parts" in self.info or "possible_keys" in self.info:
582
636
            text += "\n"
583
637
 
584
 
        text += self._hint_line("*Attached Condition:\n", "attached_condition")
 
638
        if "attached_condition_fmt" not in self.info and "attached_condition" in self.info:
 
639
            # create a formatted version of attached_condition, so it will not be too wide
 
640
            self.info["attached_condition_fmt"] = reformat_expression(self.info["attached_condition"])
 
641
 
 
642
        text += self._hint_line("*Attached Condition:\n", "attached_condition_fmt")
585
643
        if "attached_condition" in self.info:
586
644
            text += "\n"
587
645
 
607
665
        if self.parent and not isinstance(self.parent, MaterializedTableNode):
608
666
            cr.save()
609
667
            cr.set_source_rgba(0, 0, 0, 1)
610
 
            cr.set_line_width(self.get_line_width())
611
668
            if isinstance(self.parent, NestedLoopNode) and self.parent.child_aside == self:
612
 
                # special case
 
669
                # special case (line in L)
613
670
                cr.move_to(*self.varrow_source)
614
671
                cr.line_to(self.varrow_source[0], self.parent.harrow_target[1])
615
 
                cr.line_to(self.parent.harrow_target[0], self.parent.harrow_target[1])
616
 
                cr.stroke()
617
 
                draw_harrow(cr, self.parent.harrow_target, 10, 6)
 
672
                self.draw_harrow(cr, self.varrow_source[0], self.parent.harrow_target[0], self.parent.harrow_target[1])
618
673
            else:
619
 
                cr.move_to(*self.varrow_source)
620
 
                cr.line_to(self.varrow_source[0], self.parent.varrow_target[1])
621
 
                cr.stroke()
622
 
                draw_varrow(cr, (self.varrow_source[0], self.parent.varrow_target[1]), 10, 6)
623
 
            cr.fill()
 
674
                self.draw_varrow(cr, self.varrow_source[0], self.varrow_source[1], self.parent.varrow_target[1])
624
675
            self.render_cost(cr, self._figure.root_x, self.varrow_source[1] - 5)
625
676
            self.render_row_count(cr, self.varrow_source[0] + 4, self.varrow_source[1] - 5)
626
677
            cr.restore()
651
702
            if self._figure_key:
652
703
                self._figure_key.move(0, self._figure_key.y)
653
704
            child.move(self._figure.x + self._figure.width + self._context.hspacing, self._figure.y)
654
 
 
655
705
            self._width = self._figure.width + self._context.hspacing + child.width
656
706
            self._height = max(self._height, child.height)
657
707
 
662
712
 
663
713
 
664
714
class MaterializedTableNode(TableNode):
 
715
    is_container = True
 
716
 
665
717
    def __init__(self, context, name, access_type, attached_subqueries=None,\
666
718
                 materialized_from = None, materialize_attributes = None,\
667
719
                 key_name = [], info = None, cost_info = None,\
671
723
                           key_name = key_name, info = info,
672
724
                           cost_info = cost_info, rows_examined = rows_examined, rows_produced = rows_produced)
673
725
 
674
 
        # we don't expect attached_subqueries to ever have a value for this type of node,
675
 
        # if that's confirmed we can remove the arg
676
 
        assert not attached_subqueries
677
 
 
678
 
        self._figure.set_layout_flags(HFill)
679
726
        self.set_spacing(0)
680
727
        
681
728
        fill = self._figure._fill_color
682
729
        self._figure.set_color(fill[0], fill[1], fill[2], 0.8)
683
730
        self._figure.set_fill_color(fill[0], fill[1], fill[2], 0.8)
684
731
 
685
 
        self._figure_name.set_layout_flags(HFill)
686
732
        self._figure_name.set_color(0.9, 0.9, 0.9, 0.9)
687
 
        self._figure_name.set_text("%s (materialized)" % name)
 
733
        if name.startswith("<") and name.endswith(">"):
 
734
            self._figure_name.set_text(name)
 
735
        else:
 
736
            self._figure_name.set_text("%s (materialized)" % name)
688
737
        self._figure_name.set_fill_color(0.9, 0.9, 0.9, 0.9)
689
738
        self._figure_name.set_padding(4, 4, 4, 4)
690
739
 
705
754
 
706
755
    def do_relayout(self, ctx):
707
756
        self.child_materialized_from.do_relayout(ctx)
708
 
        TableNode.do_relayout(self, ctx)
 
757
        width = self.child_materialized_from.width + self._context.frame_padding*2
 
758
        # skip Table specific layouting and do everything from scratch
 
759
        VBoxFigure.do_relayout(self, ctx)
 
760
        for f in self._items:
 
761
            f.move(0, f.y)
 
762
            f._width = width
709
763
 
710
 
        if self._width < self.child_materialized_from.width + self._context.frame_padding * 2:
711
 
            self._width = int(self.child_materialized_from.width + self._context.frame_padding*2)
712
 
            VBoxFigure.do_relayout(self, ctx)
713
 
        
714
 
        self._height = int(self.inner_height + self.child_materialized_from.height + self._context.frame_padding*2)
 
764
        self._width = width
 
765
        self._height = max(self._height, int(self.inner_height + self.child_materialized_from.height + self._context.frame_padding*2))
715
766
 
716
767
        if self.width <= self.child_materialized_from.width:
717
768
            self.child_materialized_from.move(self._context.frame_padding,
718
769
                                              self.inner_height + self._context.frame_padding)
719
770
        else:
720
 
            self.child_materialized_from.move((self.width - self.child_materialized_from.width)/2,
 
771
            self.child_materialized_from.move((width - self.child_materialized_from.width)/2,
721
772
                                              self.inner_height + self._context.frame_padding)
722
773
 
 
774
        # for subselects in the WHERE list (attached subqs), attach them from the right side
 
775
        # select * from ... where (select ...) = ...
 
776
        child = self.child_attached_subqueries
 
777
        if child:
 
778
            child.do_relayout(ctx)
 
779
            child.move(width + self._context.hspacing, self._figure.y)
 
780
            self._width = width + self._context.hspacing + child.width
 
781
            self._height = max(self._height, child.height)
 
782
 
723
783
 
724
784
    def do_render(self, ctx):
725
785
        TableNode.do_render(self, ctx)
726
786
        ctx.save()
727
787
        ctx.translate(self.x, self.y)
728
 
        ctx.rectangle(0.5, 0.5, int(self._width), int(self._height)-1)
 
788
        ctx.rectangle(0.5, 0.5,
 
789
                      int(self.child_materialized_from.width + self.child_materialized_from.x + self._context.frame_padding),
 
790
                      int(self.child_materialized_from.height + self.child_materialized_from.y + self._context.frame_padding))
729
791
        ctx.set_line_width(1)
730
792
        ctx.set_dash([4.0, 2.0], 0)
731
793
        ctx.set_source_rgba(0.5, 0.5, 0.5, 0.9)
749
811
Cacheable: %(cacheable)s
750
812
""" % d
751
813
        text = text + "\n" + TableNode.get_hint_text(self)
752
 
        return text
753
814
 
754
815
 
755
816
class OperationNode(ExplainNode):
834
895
        if self.parent and not isinstance(self.parent, MaterializedTableNode) and not isinstance(self.parent, MaterializedJoinNode):
835
896
            cr.save()
836
897
            cr.set_source_rgba(0, 0, 0, 1)
837
 
            cr.set_line_width(self.get_line_width())
838
898
            if isinstance(self.parent, NestedLoopNode) and self.parent.child_aside == self:
839
899
                # special case
840
 
                cr.move_to(*self.varrow_source)
841
 
                cr.line_to(self.varrow_source[0], self.parent.harrow_target[1])
842
 
                cr.line_to(self.parent.harrow_target[0], self.parent.harrow_target[1])
843
 
                cr.stroke()
844
 
                draw_harrow(cr, self.parent.harrow_target, 10, 6)
 
900
                self.draw_harrow(cr, self.varrow_source[0], self.parent.harrow_target[0], self.parent.harrow_target[1])
845
901
            elif isinstance(self.parent, OperationNode):
846
 
                cr.move_to(*self.harrow_source)
847
 
                cr.line_to(self.parent.harrow_target[0], self.harrow_source[1])
848
 
                cr.stroke()
849
 
                draw_harrow(cr, self.parent.harrow_target, 10, 6)
 
902
                self.draw_harrow(cr, self.harrow_source[0], self.parent.harrow_target[0], self.harrow_source[1])
850
903
            else:
851
 
                cr.move_to(*self.varrow_source)
852
 
                cr.line_to(self.varrow_source[0], self.parent.varrow_target[1])
853
 
                cr.stroke()
854
 
                draw_varrow(cr, (self.varrow_source[0], self.parent.varrow_target[1]), 10, 6)
855
 
            cr.fill()
 
904
                self.draw_varrow(cr, self.varrow_source[0], self.varrow_source[1], self.parent.varrow_target[1])
856
905
            cr.restore()
857
906
 
858
907
 
932
981
        if select_list_subqueries:
933
982
            select_list_subqueries.parent = self
934
983
 
935
 
        self.set_spacing(4)
 
984
        self.set_spacing(8)
936
985
 
937
986
        if info and "select_id" in info:
938
987
            self._figure = RectangleShapeFigure("query_block #%s" % info["select_id"])
940
989
            self._figure = RectangleShapeFigure("query_block")
941
990
        self._figure.set_layout_flags(0)
942
991
        self._figure.set_padding(10, 10, 10, 10)
943
 
        self._figure.set_line_width(1)
944
992
        self.add(self._figure)
945
993
 
946
994
        if info and "message" in info:
1003
1051
        if child:
1004
1052
            child.do_relayout(ctx)
1005
1053
 
1006
 
            child.move(self.width + self._context.small_hspacing, self._figure.y)
1007
 
 
1008
 
            self._width += self._context.small_hspacing + child.width + self._context.small_hspacing
1009
 
            self._height = max(self._height, child.height)
 
1054
            child.move(self.width + self._context.small_hspacing, self._figure.y + self._context.frame_padding)
 
1055
 
 
1056
            self._width += self._context.small_hspacing + child.width
 
1057
            self._height = max(self._height, child.y + child.height)
 
1058
 
 
1059
        self._width += self._context.frame_padding * 2
 
1060
        self._height += self._context.frame_padding
1010
1061
 
1011
1062
 
1012
1063
    def get_hint_text(self):
1028
1079
 
1029
1080
    def do_render_extras(self, cr):
1030
1081
        cr.save()
 
1082
        #is_root_node = self.parent is None
 
1083
        if False:
 
1084
            # draw rectangle around the whole query block
 
1085
            cr.save()
 
1086
            cr.set_source_rgba(0, 0, 0, .3)
 
1087
            cr.set_line_width(2)
 
1088
            cr.rectangle(self.root_x - self._context.frame_padding, self._figure.root_y, self.width, self.height - self._figure.y)
 
1089
            cr.stroke()
 
1090
            cr.restore()
 
1091
 
1031
1092
        cr.set_source_rgba(0, 0, 0, 1)
1032
 
        if self.parent is None:
1033
 
            cr.set_line_width(1)
1034
 
            cr.rectangle(self._figure.root_x+2.5, self._figure.root_y+2.5, self._figure.width-4, self._figure.height-4)
1035
 
            cr.stroke()
1036
 
 
1037
1093
        self.render_cost(cr, self._figure.root_x, self._figure.root_y - 5)
1038
1094
 
1039
1095
        if self.parent and not isinstance(self.parent, MaterializedTableNode):
1040
 
            cr.set_line_width(self.get_line_width())
1041
 
            cr.move_to(*self.varrow_source)
1042
 
            cr.line_to(self.varrow_source[0], self.parent.varrow_target[1])
1043
 
            cr.stroke()
1044
 
            draw_varrow(cr, (self.varrow_source[0], self.parent.varrow_target[1]), 10, 6)
1045
 
            cr.fill()
 
1096
            self.draw_varrow(cr, self.varrow_source[0], self.varrow_source[1], self.parent.varrow_target[1])
1046
1097
        cr.restore()
1047
1098
 
1048
1099
 
1063
1114
 
1064
1115
    def set_attributes(self, attributes):
1065
1116
        self.attributes = attributes
1066
 
    
1067
 
    
 
1117
 
1068
1118
#    @property
1069
1119
#    def varrow_target(self):
1070
1120
#        return None, self.root_y + self.inner_height
1092
1142
 
1093
1143
 
1094
1144
class MaterializedJoinNode(ExplainNode):
 
1145
    is_container = True
 
1146
 
1095
1147
    def __init__(self, context, name, nested_loop, info, cost_info, attributes):
1096
1148
        ExplainNode.__init__(self, context)
1097
1149
 
1197
1249
        if self.parent and not isinstance(self.parent, MaterializedTableNode):
1198
1250
            cr.save()
1199
1251
            cr.set_source_rgba(0, 0, 0, 1)
1200
 
            cr.set_line_width(self.get_line_width())
1201
1252
            if isinstance(self.parent, NestedLoopNode):
1202
 
                cr.move_to(self.harrow_source[0], self.parent.harrow_target[1])
1203
 
                cr.line_to(*self.parent.harrow_target)
1204
 
                cr.stroke()
1205
 
                draw_harrow(cr, self.parent.harrow_target, 10, 6)
1206
 
                cr.fill()
 
1253
                self.draw_harrow(cr, self.harrow_source[0], self.parent.harrow_target[0], self.parent.harrow_target[1])
1207
1254
                self.render_row_count(cr, self.harrow_source[0] + 4, self.harrow_source[1] - 8)
1208
1255
            else:
1209
 
                cr.move_to(*self.varrow_source)
1210
 
                cr.line_to(self.varrow_source[0], self.parent.varrow_target[1])
1211
 
                cr.stroke()
1212
 
                draw_varrow(cr, (self.varrow_source[0], self.parent.varrow_target[1]), 10, 6)
1213
 
                cr.fill()
 
1256
                self.draw_varrow(cr, self.varrow_source[0], self.varrow_source[1], self.parent.varrow_target[1])
1214
1257
                self.render_row_count(cr, self.varrow_source[0] + 4, self.varrow_source[1])
1215
1258
            cr.restore()
1216
1259
 
1257
1300
        cr.set_source_rgba(0, 0, 0, 1)
1258
1301
        self.render_cost(cr, self._figure.root_x, self._figure.root_y - 5)
1259
1302
 
1260
 
        cr.set_line_width(self.get_line_width())
1261
1303
        if self.what in ("select_list_subqueries", "attached_subqueries"):
1262
1304
            # parent is a QueryBlockNode or TableNode
1263
 
            cr.move_to(self.root_x, self.harrow_source[1])
1264
1305
            target_x = self.parent._figure.root_x + self.parent._figure.width
1265
 
            cr.line_to(target_x, self.harrow_source[1])
1266
 
            cr.stroke()
1267
 
            draw_harrow(cr, (target_x, self.harrow_source[1]), -10, 6)
1268
 
            cr.fill()
 
1306
            self.draw_harrow(cr, self.root_x, target_x, self.harrow_source[1], -10, 6)
1269
1307
        elif self.parent and not isinstance(self.parent, MaterializedTableNode):
1270
 
            cr.move_to(*self.varrow_source)
1271
 
            cr.line_to(self.varrow_source[0], self.parent.varrow_target[1])
1272
 
            cr.stroke()
1273
 
            draw_varrow(cr, (self.varrow_source[0], self.parent.varrow_target[1]), 10, 6)
1274
 
            cr.fill()
 
1308
            self.draw_varrow(cr, self.varrow_source[0], self.varrow_source[1], self.parent.varrow_target[1])
1275
1309
        cr.restore()
1276
1310
 
1277
1311
 
1339
1373
    
1340
1374
    global_padding = 20
1341
1375
 
1342
 
    def __init__(self, json):
 
1376
    def __init__(self, json, server_version):
1343
1377
        self._json = json
 
1378
        self.server_version = server_version
1344
1379
        nodes = self.process_explain_output(json)
1345
1380
        if not nodes:
1346
1381
            log_error("Could not process JSON data\n")
1352
1387
                s = StringIO.StringIO()
1353
1388
                self._root.dump(s)
1354
1389
                print s.getvalue()
1355
 
        
 
1390
 
 
1391
        self._old_offset = None
 
1392
        self.overview_mode = False
 
1393
 
 
1394
        self._scale = 1.0
1356
1395
        self._offset = (0, 0)
 
1396
        self._extra_offset = (0, 0)
1357
1397
        self._canvas = None
1358
1398
        self._view = None
1359
1399
        self.size = None
1370
1410
 
1371
1411
 
1372
1412
    def handle_table(self, table):
1373
 
        name = table["table_name"]
 
1413
        name = table.get("table_name", "")
1374
1414
        materialized_from_subquery = table.get("materialized_from_subquery")
1375
1415
        materialized_from_subquery_node = None
1376
1416
        materialized_attributes = {}
1394
1434
                             materialized_from=materialized_from_subquery_node,
1395
1435
                             materialize_attributes=materialized_attributes,
1396
1436
                             attached_subqueries=attached_subqueries, # ??
1397
 
                             access_type=table['access_type'],
 
1437
                             access_type=table.get('access_type', ""),
1398
1438
                             key_name=table.get('key', None),
1399
1439
                             info=table,
1400
1440
                             cost_info=table.get('cost_info'),
1404
1444
        else:
1405
1445
            return TableNode(self, name,
1406
1446
                             attached_subqueries=attached_subqueries,
1407
 
                             access_type=table['access_type'],
 
1447
                             access_type=table.get('access_type', ""),
1408
1448
                             key_name=table.get('key', None),
1409
1449
                             info=table,
1410
1450
                             cost_info=table.get('cost_info'),
1473
1513
                    qblock = self.handle_query_block(key, value, True)
1474
1514
                elif key in ("dependent", "cacheable", "using_temporary_table"):
1475
1515
                    attributes[key] = value
 
1516
                elif key == "table": # handles bug #18997475
 
1517
                    qblock = self.handle_table(value)
1476
1518
                else:
1477
1519
                    self.unexpected(key, name)
1478
 
            qblock.set_attributes(attributes)
 
1520
            if attributes:
 
1521
                qblock.set_attributes(attributes)
1479
1522
            subqueries.append(qblock)
1480
1523
        return SubQueries(self, name, subqueries)
1481
1524
 
1610
1653
    tooltip = property(_get_tooltip, _set_tooltip)
1611
1654
 
1612
1655
 
1613
 
    def init_canvas(self, view, queue_repaint_cb):
 
1656
    def init_canvas(self, view, scroll, queue_repaint_cb):
1614
1657
        self._canvas = MyCanvas(queue_repaint_cb)
1615
1658
        self._canvas.add(self._root)
1616
1659
 
1617
1660
        self._view = view
 
1661
        self._scroll = scroll
1618
1662
 
1619
1663
        self._root._figure.set_fill_color(0.8, 0.8, 0.8, 1)
1620
1664
 
1624
1668
 
1625
1669
 
1626
1670
    def client_to_screen(self, x, y):
1627
 
        x += self._offset[0]
1628
 
        y += self._offset[1]
 
1671
        x += (self._offset[0] + self._extra_offset[0])
 
1672
        y += (self._offset[1] + self._extra_offset[1])
1629
1673
        return self._view.client_to_screen(int(x), int(y))
1630
1674
 
1631
 
 
1632
1675
    def export_to_png(self, path):
1633
1676
        img = ImageSurface(width=self.size[0], height=self.size[1])
1634
1677
        cr = Context(img)
1644
1687
 
1645
1688
        w, h = self._root.size
1646
1689
        self.size = w + self.global_padding * 2, h + self.global_padding * 2
1647
 
        
1648
1690
        return self.size
1649
1691
    
1650
1692
    
1651
1693
    def repaint(self, cr):
1652
 
        cr.translate(self._offset[0], self._offset[1])
 
1694
        cr.translate(self._offset[0] + self._extra_offset[0], self._offset[1] + self._extra_offset[1])
 
1695
        cr.scale(self._scale, self._scale)
1653
1696
        try:
1654
 
            #cr.set_source_rgba(0, 0, 0, 1)
1655
 
            #cr.rectangle(0, 0, self.size[0], self.size[1])
1656
 
            #cr.stroke()
1657
1697
            self._canvas.repaint(cr, 0, 0, self.size[0], self.size[1])
 
1698
 
 
1699
            if self.overview_mode:
 
1700
                x, y, w, h = self._overview_visible_rect
 
1701
 
 
1702
                total_w, total_h = self.size
 
1703
                total_w = max(total_w, w)
 
1704
                total_h = max(total_h, h)
 
1705
 
 
1706
                cr.set_line_width(1)
 
1707
                cr.rectangle(0, 0, total_w, total_h)
 
1708
                cr.new_sub_path()
 
1709
                cr.rectangle(x, y, w, h)
 
1710
                cr.set_fill_rule(cairo.CAIRO_FILL_RULE_EVEN_ODD)
 
1711
                cr.set_source_rgba(0, 0, 0, 0.3)
 
1712
                cr.fill()
 
1713
                cr.rectangle(x, y, w, h)
 
1714
                cr.set_source_rgba(0, 0, 0, 0.5)
 
1715
                cr.stroke()
1658
1716
        except Exception:
1659
1717
            import traceback
1660
1718
            log_error("Exception repainting explain: %s\n" % traceback.format_exc())
1661
1719
 
1662
1720
 
 
1721
    def enter_overview_mode(self):
 
1722
        self._old_offset = self._offset
 
1723
        self._old_scale = self._scale
 
1724
 
 
1725
        r = self._scroll.get_content_rect()
 
1726
        _, _, view_width, view_height = r
 
1727
        # calculate how much we have to scale to fit the whole diagram on screen
 
1728
        total_width, total_height = self.size
 
1729
 
 
1730
        # check if whole thing already fits on screen
 
1731
        if total_width <= view_width and total_height <= view_height:
 
1732
            return
 
1733
 
 
1734
        dw = float(total_width) / view_width
 
1735
        dh = float(total_height) / view_height
 
1736
        if dw > dh:
 
1737
            self._scale = 1/dw
 
1738
        else:
 
1739
            self._scale = 1/dh
 
1740
 
 
1741
        self.overview_mode = True
 
1742
        self._overview_visible_rect = r
 
1743
 
 
1744
        # extra offset needed to center contents
 
1745
        self._extra_offset = (view_width - total_width * self._scale)/2, (view_height - total_height * self._scale)/2
 
1746
 
 
1747
        self._view.set_back_color("#b3b3b3")
 
1748
        self._view.set_size(int(view_width), int(view_height))
 
1749
        self._view.set_needs_repaint()
 
1750
 
 
1751
 
 
1752
    def leave_overview_mode(self):
 
1753
        x, y, w, h = self._overview_visible_rect
 
1754
        self.overview_mode = False
 
1755
        self._scale = self._old_scale
 
1756
        self._extra_offset = (0, 0)
 
1757
        self._view.set_back_color("#ffffff")
 
1758
        self._view.set_size(max(self._scroll.get_width(), self.size[0]), max(self._scroll.get_height(), self.size[1]))
 
1759
        self._scroll.scroll_to(x, y)
 
1760
        self._view.set_needs_repaint()
 
1761
 
 
1762
 
 
1763
    def mouse_moved(self, x, y):
 
1764
        x -= (self._offset[0] + self._extra_offset[0])
 
1765
        y -= (self._offset[1] + self._extra_offset[1])
 
1766
        x /= self._scale
 
1767
        y /= self._scale
 
1768
        total_width, total_height = self.size
 
1769
        xx, yy, ww, hh = self._overview_visible_rect
 
1770
        x -= ww / 2
 
1771
        y -= hh / 2
 
1772
        ww = min(ww, total_width)
 
1773
        hh = min(hh, total_height)
 
1774
        x = min(max(x, 0), total_width - ww)
 
1775
        y = min(max(y, 0), total_height - hh)
 
1776
        self._overview_visible_rect = int(x), int(y), ww, hh
 
1777
        self._view.set_needs_repaint()
 
1778
 
 
1779
 
 
1780
    def mouse_down(self, x, y):
 
1781
        self.leave_overview_mode()
 
1782
 
 
1783
 
1663
1784
    def fmt_cost(self, value):
1664
1785
        if self.cost_value_is_amount:
1665
1786
            return fmt_number(value)