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:
parent
bf6f295579
commit
8439410c5b
@ -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' );
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
27
sql/patch-009.sql
Normal 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
27
sql/patch-010.sql
Normal 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;
|
@ -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
|
||||
|
@ -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">
|
||||
|
Loading…
Reference in New Issue
Block a user