diff --git a/cpanfile b/cpanfile index bc9794e..b9db402 100644 --- a/cpanfile +++ b/cpanfile @@ -18,7 +18,7 @@ requires 'Encode'; requires 'English'; requires 'File::Spec'; requires 'Mojolicious'; -requires 'Pg::Explain', '>= 1.13'; +requires 'Pg::Explain', '>= 2.0'; requires 'Number::Bytes::Human'; # vim: set ft=perl: diff --git a/lib/Explain.pm b/lib/Explain.pm index 829fada..d6b9aac 100755 --- a/lib/Explain.pm +++ b/lib/Explain.pm @@ -40,6 +40,9 @@ sub startup { # load number_format plugin $self->plugin( 'number_format' ); + # Plugin to to smart quoting of DB identifiers + $self->plugin( 'DBIdentQuote' ); + # Plugin to check if static file exists $self->plugin( 'Explain::Plugin::StaticExists' ); diff --git a/lib/Explain/Controller.pm b/lib/Explain/Controller.pm index 71fca27..73ce853 100755 --- a/lib/Explain/Controller.pm +++ b/lib/Explain/Controller.pm @@ -5,6 +5,7 @@ use Mojo::Base 'Mojolicious::Controller'; use English -no_match_vars; use Pg::Explain; +use Pg::Explain::Hinter; use pgFormatter::Beautify; use Encode; use Email::Valid; @@ -268,7 +269,7 @@ sub delete { my $key = defined $self->stash->{ key } ? $self->stash->{ key } : ''; # missing or invalid - return $self->redirect_to( 'new-explain' ) unless $id =~ m{\A[a-zA-Z0-9]+\z}; + return $self->redirect_to( 'new-explain' ) unless $id =~ m{\A[a-zA-Z0-9]+\z}; return $self->redirect_to( 'new-explain' ) unless $key =~ m{\A[a-zA-Z0-9]+\z}; # delete plan in database @@ -356,6 +357,10 @@ sub show { $self->stash->{ optimization_path } = $self->database->get_optimization_path( $id ); $self->stash->{ suboptimizations } = $self->database->get_optimizations_for( $id ); + # Add hinter, if it makes sense + my $hinter = Pg::Explain::Hinter->new( $explain ); + $self->stash->{ hinter } = $hinter if $hinter->any_hints; + # render will be called automatically return $self->render( 'controller/iframe' ) if 'iframe' eq $self->match->endpoint->name; @@ -471,8 +476,8 @@ sub info { my @versions = (); for my $module ( sort keys %INC ) { next if $module =~ m{^\.?/}; - $module =~ s/\.pm$//; - $module =~ s#/#::#g; + $module =~ s/\.pm$//; + $module =~ s#/#::#g; push @versions, { 'module' => $module, diff --git a/lib/Explain/Plugin/DBIdentQuote.pm b/lib/Explain/Plugin/DBIdentQuote.pm new file mode 100644 index 0000000..7b81c96 --- /dev/null +++ b/lib/Explain/Plugin/DBIdentQuote.pm @@ -0,0 +1,21 @@ +package Explain::Plugin::DBIdentQuote; + +use Number::Bytes::Human qw(format_bytes); +use Mojo::Base 'Mojolicious::Plugin'; + +sub register { + my ( $self, $app ) = @_; + + # register helpers + $app->helper( db_ident_quote => \&db_ident_quote ); +} + +sub db_ident_quote { + my $self = shift; + local $_ = shift; + + return $_ if /\A[a-z][a-z0-9_]*\z/; + return sprintf '"%s"', $_; +} + +1; diff --git a/public/css/common.css b/public/css/common.css index c4419d7..aff9de0 100755 --- a/public/css/common.css +++ b/public/css/common.css @@ -117,7 +117,7 @@ section p a:hover {background:#234;color:#fff;border:1px solid #123} .result .tabs ul {margin:0;padding:0;list-style:none} .result .tabs li {display:block;float:left;margin-right:1px} -.result .tabs a {display:block;padding:4px 12px;background:#cfcfcf;color:#fff;text-decoration:none;font-weight:bold} +.result .tabs a {display:block;padding:4px 12px;background:#ababab;color:#fff;text-decoration:none;font-weight:bold} .result .tabs a:hover {color:#234} .result .tabs a.current, .result .tabs a.current:hover {background:#234;color:#fff} @@ -285,3 +285,19 @@ section p a:hover {background:#234;color:#fff;border:1px solid #123} #plan-settings th { font-size: 1.2em; font-weight: bold; padding: 0.2em 0.5em;} #plan-settings thead th {text-align: center; font-size: 1.5em;} #plan-settings td {text-align: right; padding: 0.2em 0.5em;} + +.result-hints h1 { + border: none; + padding: 10px 20px; + color: #444; +} +.result-hints ol { + padding-left: 20px; +} +.result-hints ol li { + margin-top: 20px; +} +.result-hints ol li p, +.result-hints ol li pre { + margin: 5px; +} diff --git a/public/css/style.css b/public/css/style.css index 8e5c24e..8258e3d 100755 --- a/public/css/style.css +++ b/public/css/style.css @@ -90,7 +90,7 @@ div.explain-form a:hover {background:#e5ecf9} * html #explain-form input {border:0} *:first-child+html #explain-form input {border:0} - .result-text, .result-source, .result-html, .result-stats {margin:0;padding:0;border:1px solid #ccc;overflow:auto} + .result-text, .result-source, .result-html, .result-stats, .result-hints {margin:0;padding:0;border:1px solid #ccc;overflow:auto} /* all ico(ns) */ .result-html table#explain td.n div.ico {position:absolute;top:-4px;left:-22px;width:22px;height:22px;background:url('../img/sprite.png') no-repeat 0px -180px;text-indent:-1000px;overflow:hidden} @@ -205,3 +205,12 @@ button.copy { background: #e5ecf9; color: #444; } + +div.tabs span.newblink { + margin: 10px; + animation: newblink .3s alternate infinite; +} +@keyframes newblink { + 0% { color: #ff0000; } + 100% { color: #ffff00; } +} diff --git a/public/js/explain.js b/public/js/explain.js index 215932a..b504813 100644 --- a/public/js/explain.js +++ b/public/js/explain.js @@ -340,7 +340,7 @@ return; } - if(['html', 'source', 'text', 'query', 'bquery', 'stats'].indexOf(want) == -1 ) { + if(['html', 'source', 'text', 'hints', 'query', 'bquery', 'stats'].indexOf(want) == -1 ) { return; } diff --git a/templates/controller/show.html.ep b/templates/controller/show.html.ep index b9a2463..eb144b2 100755 --- a/templates/controller/show.html.ep +++ b/templates/controller/show.html.ep @@ -112,7 +112,8 @@ % } % my $global_node_id = 0; - +% my $table_node_id_to_explain_node_id = {}; +% my $explain_node_id_to_table_node_id = {}; % my $prev_row_level = 0; @@ -212,6 +213,8 @@ % $row_class .= ' c-' . $row_color; % my $node_id = $global_node_id++; + % $table_node_id_to_explain_node_id->{$global_node_id} = $node->id; + % $explain_node_id_to_table_node_id->{$node->id} = $global_node_id;