1
0
mirror of https://gitlab.com/depesz/explain.depesz.com.git synced 2024-11-24 08:42:27 +02:00

Add "optimizations" for plans

This commit is contained in:
Hubert depesz Lubaczewski 2017-05-19 18:45:41 +02:00
parent bf6f295579
commit 8439410c5b
7 changed files with 178 additions and 8 deletions

View File

@ -41,6 +41,9 @@ sub startup {
# route: 'index'
$routes->route( '/' )->to( 'controller#index' )->name( 'new-explain' );
# route: 'new-optimization'
$routes->route( '/new_optimization' )->to( 'controller#new_optimization' )->name( 'new-optimization' );
# route: 'user-history'
$routes->route( '/user-history/:direction/:key' )->to( 'controller#user_history', direction => undef, key => undef )->name( 'user-history' );

View File

@ -161,6 +161,24 @@ sub login {
return;
}
sub new_optimization {
my $self = shift;
my $original_plan_id = $self->req->param( 'original' ) // '';
return $self->redirect_to( 'new-explain' ) unless $original_plan_id =~ m{\A[a-zA-Z0-9]+\z};
my ( $original_plan, $original_title ) = $self->database->get_plan( $original_plan_id );
return $self->redirect_to( 'new-explain', status => 404 ) unless $original_plan;
$self->stash->{ 'optimization' } = 1;
$self->stash->{ 'original_plan_id' } = $original_plan_id;
$self->stash->{ 'original_title' } = $original_title;
return $self->render( 'controller/index' );
}
sub index {
my $self = shift;
@ -174,6 +192,12 @@ sub index {
return $self->render( message => 'Your plan is too long.', status => 413 )
if 10_000_000 < length $plan;
# Get id of parent plan
my $parent_id = $self->req->param( 'optimization_for' );
if ( defined $parent_id ) {
$parent_id = undef unless $self->database->plan_exists( $parent_id );
}
# public
my $is_public = $self->req->param( 'is_public' ) ? 1 : 0;
@ -214,7 +238,7 @@ sub index {
}
# save to database
my ( $id, $delete_key ) = $self->database->save_with_random_name( $title, $plan, $is_public, $is_anon, $self->session->{ 'user' } );
my ( $id, $delete_key ) = $self->database->save_with_random_name( $title, $plan, $is_public, $is_anon, $self->session->{ 'user' }, $parent_id, );
# redirect to /show/:id
$self->flash( delete_key => $delete_key );
@ -306,6 +330,10 @@ sub show {
$self->stash->{ title } = $title;
$self->stash->{ stats } = $stats;
# Fetch path of optimizations
$self->stash->{ optimization_path } = $self->database->get_optimization_path( $id );
$self->stash->{ suboptimizations } = $self->database->get_optimizations_for( $id );
# render will be called automatically
return;
}

View File

@ -211,12 +211,12 @@ sub update_plan {
sub save_with_random_name {
my $self = shift;
my ( $title, $content, $is_public, $is_anon, $username ) = @_;
my ( $title, $content, $is_public, $is_anon, $username, $optimization_for ) = @_;
my @row = $self->dbh->selectrow_array(
'SELECT id, delete_key FROM register_plan(?, ?, ?, ?, ?)',
'SELECT id, delete_key FROM register_plan(?, ?, ?, ?, ?, ?)',
undef,
$title, $content, $is_public, $is_anon, $username,
$title, $content, $is_public, $is_anon, $username, $optimization_for,
);
# return id and delete_key
@ -242,7 +242,7 @@ sub get_plan {
my ( $plan_id ) = @_;
my @row = $self->dbh->selectrow_array(
'SELECT plan, title FROM plans WHERE id = ? AND NOT is_deleted',
'SELECT plan, title, optimization_for FROM plans WHERE id = ? AND NOT is_deleted',
undef,
$plan_id,
);
@ -251,6 +251,60 @@ sub get_plan {
return @row;
}
sub get_optimization_path {
my $self = shift;
my ($plan_id) = @_;
my $rows = $self->dbh->selectall_arrayref(
'
WITH RECURSIVE path AS (
SELECT id, title, optimization_for, 0 as level FROM plans WHERE id = ? and not is_deleted
union all
SELECT p.id, p.title, p.optimization_for, x.level + 1
FROM path x
join plans p on p.id = x.optimization_for
WHERE NOT p.is_deleted
AND x.optimization_for IS NOT NULL
)
SELECT
id, title
FROM
path
ORDER BY level desc;
',
{ Slice => {} },
$plan_id,
);
return if 0 == scalar @{ $rows };
return if 1 == scalar @{ $rows };
return $rows;
}
sub get_optimizations_for {
my $self = shift;
my ($plan_id) = @_;
my $rows = $self->dbh->selectall_arrayref(
'select id, title from plans where optimization_for = ? and not is_deleted',
{ Slice => {} },
$plan_id
);
return if 0 == scalar @{ $rows };
return $rows;
}
sub plan_exists {
my $self = shift;
my ( $plan_id ) = @_;
my @row = $self->dbh->selectrow_array(
'SELECT 1 FROM plans WHERE id = ? AND NOT is_deleted',
undef,
$plan_id,
);
return if 0 == scalar @row;
return 1;
}
sub delete_plan {
my $self = shift;
my ( $plan_id, $delete_key ) = @_;

27
sql/patch-009.sql Normal file
View File

@ -0,0 +1,27 @@
BEGIN;
ALTER TABLE public.plans add column optimization_for TEXT;
DO $$
DECLARE
v_part_name TEXT;
v_sql TEXT;
BEGIN
FOR v_part_name IN
SELECT
c.relname
FROM
pg_catalog.pg_inherits i
JOIN pg_class c ON i.inhrelid = c.oid
JOIN pg_namespace n ON c.relnamespace = n.oid
WHERE
i.inhparent = 'public.plans'::regclass
AND n.nspname = 'plans'
ORDER BY c.relname
LOOP
raise notice 'Adding index on plans.% (optimization_for)', v_part_name;
v_sql := format( 'CREATE INDEX %I ON plans.%I (optimization_for)', v_part_name || '_optimization_for', v_part_name );
execute v_sql;
END LOOP;
END;
$$;
COMMIT;

27
sql/patch-010.sql Normal file
View File

@ -0,0 +1,27 @@
BEGIN;
CREATE OR REPLACE FUNCTION register_plan(in_title text, in_plan text, in_is_public boolean, in_is_anonymized boolean, in_username text, in_optimization_for TEXT) RETURNS register_plan_return
LANGUAGE plpgsql
AS $$
DECLARE
use_hash_length int4 := 2;
reply register_plan_return;
use_sql TEXT;
BEGIN
reply.delete_key := get_random_string( 50 );
LOOP
reply.id := get_random_string(use_hash_length);
use_sql := format( 'INSERT INTO plans.%I (id, title, plan, is_public, entered_on, is_anonymized, delete_key, added_by, optimization_for) VALUES ($1, $2, $3, $4, now(), $5, $6, $7, $8 )', 'part_' || substr(reply.id, 1, 1) );
BEGIN
execute use_sql using reply.id, in_title, in_plan, in_is_public, in_is_anonymized, reply.delete_key, in_username, in_optimization_for;
RETURN reply;
EXCEPTION WHEN unique_violation THEN
-- do nothing
END;
use_hash_length := use_hash_length + 1;
IF use_hash_length >= 30 THEN
raise exception 'Random string of length == 30 requested. something''s wrong.';
END IF;
END LOOP;
END;
$$;
COMMIT;

View File

@ -1,6 +1,7 @@
% layout 'default';
% my $title = 'New explain';
% $title = 'New optimization for plan #' . stash('original_plan_id') if stash('optimization');
% title $title;
<h1><%= $title =%></h1>
@ -18,11 +19,20 @@
</div>
% }
<form id="new-explain" method="post" action="<%= url_for 'current' %>">
<form id="new-explain" method="post" action="<%= url_for 'new-explain' %>">
<div class="fe fe-first fe_plan">
<label for="title">Optional title for plan:</label>
% if ( stash('optimization') ) {
% my $new_title = "Optimization for: ";
% $new_title .= stash('original_title') . ";" if stash('original_title');
% $new_title .= " plan #" . stash('original_plan_id');
<input id="title" name="title" value="<%= $new_title %>"/>
<input id="optimization_for" name="optimization_for" type="hidden" value="<%= stash('original_plan_id') %>"/>
% } else {
<input id="title" name="title" class="auto-hint" title="Optional title"/>
% }
<label for="plan">Paste your explain/explain analyze here:</label>
<textarea id="plan" name="plan" class="auto-hint" title="For example:
QUERY PLAN

View File

@ -420,13 +420,34 @@
</div>
% if ( stash('optimization_path') ) {
<h3>Optimization path:</h3>
<ul>
% for my $opt ( @{ stash('optimization_path') }) {
<li><a href="<%= url_for( 'show', id => $opt->{'id'} ) =%>">#<%= $opt->{'id'} %> : <%= $opt->{'title'} %></a></li>
% }
</ul>
</h3>
% }
% if ( stash('suboptimizations') ) {
<h3>Optimization(s) for this plan:</h3>
<ul>
% for my $opt ( @{ stash('suboptimizations') }) {
<li><a href="<%= url_for( 'show', id => $opt->{'id'} ) =%>">#<%= $opt->{'id'} %> : <%= $opt->{'title'} %></a></li>
% }
</ul>
% }
<div class="result">
<div class="plea">
Did it help? <a href="http://www.depesz.com/how-about-donating/">Consider supporting us</a>.
<form id="new-optimization" method="post" action="<%= url_for 'new-optimization' %>">
<input type="hidden" name="original" value="<%= $id %>"/>
<button type="submit" name="add-optimization" id="add-optimization"><span>Add optimization</span></button>
</form>
</div>
<div class="tabs">
<ul class="clearfix">
<li class="html">