You've already forked explain.depesz.com
mirror of
https://gitlab.com/depesz/explain.depesz.com.git
synced 2025-07-13 01:30:14 +02:00
Add support for storing queries
This commit is contained in:
@ -210,6 +210,12 @@ sub index {
|
|||||||
$title = '' unless defined $title;
|
$title = '' unless defined $title;
|
||||||
$title = '' if 'Optional title' eq $title;
|
$title = '' if 'Optional title' eq $title;
|
||||||
|
|
||||||
|
my $query = $self->req->param( 'query' );
|
||||||
|
if ( defined $query ) {
|
||||||
|
$query = undef if $query =~ m{\A\s*\z};
|
||||||
|
$query = undef if $query =~ m{\A\s*for\s+example:\s+select\s+a,\s+b\s+from\s+c\s+where\s+d\s+>\s+now\(\)\s+-\s+'5\s+minutes'::interval\s*;\s*\z}ism;
|
||||||
|
}
|
||||||
|
|
||||||
# try
|
# try
|
||||||
eval {
|
eval {
|
||||||
|
|
||||||
@ -222,7 +228,12 @@ sub index {
|
|||||||
|
|
||||||
# Anonymize plan, when requested.
|
# Anonymize plan, when requested.
|
||||||
if ( $is_anon ) {
|
if ( $is_anon ) {
|
||||||
$explain->anonymize();
|
if ( $query ) {
|
||||||
|
( $query ) = $explain->anonymize( $query );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$explain->anonymize();
|
||||||
|
}
|
||||||
$plan = $explain->as_text();
|
$plan = $explain->as_text();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -239,7 +250,7 @@ sub index {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# save to database
|
# save to database
|
||||||
my ( $id, $delete_key ) = $self->database->save_with_random_name( $title, $plan, $is_public, $is_anon, $self->session->{ 'user' }, $parent_id, );
|
my ( $id, $delete_key ) = $self->database->save_with_random_name( $title, $plan, $is_public, $is_anon, $self->session->{ 'user' }, $parent_id, $query );
|
||||||
|
|
||||||
# redirect to /show/:id
|
# redirect to /show/:id
|
||||||
$self->flash( delete_key => $delete_key );
|
$self->flash( delete_key => $delete_key );
|
||||||
@ -273,19 +284,19 @@ sub show {
|
|||||||
my $self = shift;
|
my $self = shift;
|
||||||
|
|
||||||
# value of "/:id" param
|
# value of "/:id" param
|
||||||
my $id = defined $self->stash->{ id } ? $self->stash->{ id } : '';
|
my $id = defined $self->stash->{ id } ? $self->stash->{ id } : '';
|
||||||
|
|
||||||
# missing or invalid
|
# 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};
|
||||||
|
|
||||||
# get plan source from database
|
# get plan source from database
|
||||||
my ( $plan, $title ) = $self->database->get_plan( $id );
|
my $data = $self->database->get_plan_data( $id );
|
||||||
|
|
||||||
# not found in database
|
# not found in database
|
||||||
return $self->redirect_to( 'new-explain', status => 404 ) unless $plan;
|
return $self->redirect_to( 'new-explain', status => 404 ) unless $data;
|
||||||
|
|
||||||
# make explanation
|
# make explanation
|
||||||
my $explain = eval { Pg::Explain->new( source => $plan ); };
|
my $explain = eval { Pg::Explain->new( source => $data->{ 'plan' } ); };
|
||||||
|
|
||||||
# plans are validated before save, so this should never happen
|
# plans are validated before save, so this should never happen
|
||||||
if ( $EVAL_ERROR ) {
|
if ( $EVAL_ERROR ) {
|
||||||
@ -328,7 +339,8 @@ sub show {
|
|||||||
|
|
||||||
# put explain and title to stash
|
# put explain and title to stash
|
||||||
$self->stash->{ explain } = $explain;
|
$self->stash->{ explain } = $explain;
|
||||||
$self->stash->{ title } = $title;
|
$self->stash->{ title } = $data->{ 'title' };
|
||||||
|
$self->stash->{ query } = $data->{ 'query' };
|
||||||
$self->stash->{ stats } = $stats;
|
$self->stash->{ stats } = $stats;
|
||||||
|
|
||||||
# Fetch path of optimizations
|
# Fetch path of optimizations
|
||||||
|
@ -215,12 +215,12 @@ sub update_plan {
|
|||||||
|
|
||||||
sub save_with_random_name {
|
sub save_with_random_name {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
my ( $title, $content, $is_public, $is_anon, $username, $optimization_for ) = @_;
|
my ( $title, $content, $is_public, $is_anon, $username, $optimization_for, $query ) = @_;
|
||||||
|
|
||||||
my @row = $self->dbh->selectrow_array(
|
my @row = $self->dbh->selectrow_array(
|
||||||
'SELECT id, delete_key FROM register_plan(?, ?, ?, ?, ?, ?)',
|
'SELECT id, delete_key FROM register_plan(?, ?, ?, ?, ?, ?, ?)',
|
||||||
undef,
|
undef,
|
||||||
$title, $content, $is_public, $is_anon, $username, $optimization_for,
|
$title, $content, $is_public, $is_anon, $username, $optimization_for, $query
|
||||||
);
|
);
|
||||||
|
|
||||||
# return id and delete_key
|
# return id and delete_key
|
||||||
|
@ -108,6 +108,8 @@ section p a:hover {background:#234;color:#fff;border:1px solid #123}
|
|||||||
.result {margin:0;padding:0}
|
.result {margin:0;padding:0}
|
||||||
.result-source {background:#222 url('../img/code-bg.gif');color:#fff}
|
.result-source {background:#222 url('../img/code-bg.gif');color:#fff}
|
||||||
.result-source pre {margin:0;padding:1em}
|
.result-source pre {margin:0;padding:1em}
|
||||||
|
.result-query {background:#222 url('../img/code-bg.gif');color:#fff}
|
||||||
|
.result-query pre {margin:0;padding:1em}
|
||||||
.result-text {background:#222 url('../img/code-bg.gif');color:#fff}
|
.result-text {background:#222 url('../img/code-bg.gif');color:#fff}
|
||||||
.result-text pre {margin:0;padding:1em}
|
.result-text pre {margin:0;padding:1em}
|
||||||
|
|
||||||
|
@ -195,10 +195,19 @@
|
|||||||
|
|
||||||
var result = $( link.parents( 'div.result' ).get( 0 ) );
|
var result = $( link.parents( 'div.result' ).get( 0 ) );
|
||||||
|
|
||||||
|
if ( 'query' == view.toLowerCase( ) ) {
|
||||||
|
result.find( 'div.result-html' ).hide( );
|
||||||
|
result.find( 'div.result-stats' ).hide( );
|
||||||
|
result.find( 'div.result-source' ).hide( );
|
||||||
|
result.find( 'div.result-query' ).show( );
|
||||||
|
result.find( 'div.result-text' ).hide( );
|
||||||
|
return;
|
||||||
|
}
|
||||||
if ( 'text' == view.toLowerCase( ) ) {
|
if ( 'text' == view.toLowerCase( ) ) {
|
||||||
result.find( 'div.result-html' ).hide( );
|
result.find( 'div.result-html' ).hide( );
|
||||||
result.find( 'div.result-stats' ).hide( );
|
result.find( 'div.result-stats' ).hide( );
|
||||||
result.find( 'div.result-source' ).hide( );
|
result.find( 'div.result-source' ).hide( );
|
||||||
|
result.find( 'div.result-query' ).hide( );
|
||||||
result.find( 'div.result-text' ).show( );
|
result.find( 'div.result-text' ).show( );
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -207,6 +216,7 @@
|
|||||||
result.find( 'div.result-html' ).show( );
|
result.find( 'div.result-html' ).show( );
|
||||||
result.find( 'div.result-stats' ).hide( );
|
result.find( 'div.result-stats' ).hide( );
|
||||||
result.find( 'div.result-source' ).hide( );
|
result.find( 'div.result-source' ).hide( );
|
||||||
|
result.find( 'div.result-query' ).hide( );
|
||||||
result.find( 'div.result-text' ).hide( );
|
result.find( 'div.result-text' ).hide( );
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -215,6 +225,7 @@
|
|||||||
result.find( 'div.result-html' ).hide( );
|
result.find( 'div.result-html' ).hide( );
|
||||||
result.find( 'div.result-stats' ).hide( );
|
result.find( 'div.result-stats' ).hide( );
|
||||||
result.find( 'div.result-source' ).show( );
|
result.find( 'div.result-source' ).show( );
|
||||||
|
result.find( 'div.result-query' ).hide( );
|
||||||
result.find( 'div.result-text' ).hide( );
|
result.find( 'div.result-text' ).hide( );
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -222,6 +233,7 @@
|
|||||||
result.find( 'div.result-html' ).hide( );
|
result.find( 'div.result-html' ).hide( );
|
||||||
result.find( 'div.result-source' ).hide( );
|
result.find( 'div.result-source' ).hide( );
|
||||||
result.find( 'div.result-stats' ).show( );
|
result.find( 'div.result-stats' ).show( );
|
||||||
|
result.find( 'div.result-query' ).hide( );
|
||||||
result.find( 'div.result-text' ).hide( );
|
result.find( 'div.result-text' ).hide( );
|
||||||
},
|
},
|
||||||
|
|
||||||
|
33
sql/patch-012.sql
Normal file
33
sql/patch-012.sql
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
-- Added support for queries for plans
|
||||||
|
|
||||||
|
BEGIN;
|
||||||
|
ALTER TABLE public.plans add column query TEXT;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION public.register_plan(in_title text, in_plan text, in_is_public boolean, in_is_anonymized boolean, in_username text, in_optimization_for text, in_query TEXT)
|
||||||
|
RETURNS register_plan_return
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $function$
|
||||||
|
DECLARE
|
||||||
|
use_hash_length int4 := 2;
|
||||||
|
reply register_plan_return;
|
||||||
|
insert_sql TEXT;
|
||||||
|
BEGIN
|
||||||
|
insert_sql := 'INSERT INTO public.plans (id, title, plan, is_public, entered_on, is_anonymized, delete_key, added_by, optimization_for, query) VALUES ($1, $2, $3, $4, now(), $5, $6, $7, $8, $9 )';
|
||||||
|
reply.delete_key := get_random_string( 50 );
|
||||||
|
LOOP
|
||||||
|
reply.id := get_random_string(use_hash_length);
|
||||||
|
BEGIN
|
||||||
|
execute insert_sql using reply.id, in_title, in_plan, in_is_public, in_is_anonymized, reply.delete_key, in_username, in_optimization_for, in_query;
|
||||||
|
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;
|
||||||
|
$function$;
|
||||||
|
|
||||||
|
commit;
|
@ -51,6 +51,14 @@
|
|||||||
Buckets: 1024 Batches: 1 Memory Usage: 1kB
|
Buckets: 1024 Batches: 1 Memory Usage: 1kB
|
||||||
-> Seq Scan on pg_namespace n (cost=0.00..1.09 rows=4 width=68) (actual time=0.005..0.007 rows=4 loops=1)
|
-> Seq Scan on pg_namespace n (cost=0.00..1.09 rows=4 width=68) (actual time=0.005..0.007 rows=4 loops=1)
|
||||||
Filter: ((nspname <> 'pg_catalog'::name) AND (nspname <> 'information_schema'::name))"></textarea>
|
Filter: ((nspname <> 'pg_catalog'::name) AND (nspname <> 'information_schema'::name))"></textarea>
|
||||||
|
|
||||||
|
<label for="query">Optionally paste your query here:</label>
|
||||||
|
<textarea id="query" name="query" class="auto-hint" title="For example:
|
||||||
|
SELECT a, b
|
||||||
|
FROM c
|
||||||
|
WHERE d > now() - '5 minutes'::interval;"></textarea>
|
||||||
|
select\s+a,\s+b\s+from\s+c\s+where\s+d\s+>\s+now()\s+-\s+'5\s+minutes'::interval;
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="fe fe_is_public">
|
<div class="fe fe_is_public">
|
||||||
|
@ -475,6 +475,14 @@
|
|||||||
onclick="$( this ).explain( 'toggleView', 'text', this ); return false;"
|
onclick="$( this ).explain( 'toggleView', 'text', this ); return false;"
|
||||||
onkeypress="return this.onclick( );">TEXT</a>
|
onkeypress="return this.onclick( );">TEXT</a>
|
||||||
</li>
|
</li>
|
||||||
|
% }
|
||||||
|
% if ( stash('query') ) {
|
||||||
|
<li class="query">
|
||||||
|
<a href="#query"
|
||||||
|
title="view query"
|
||||||
|
onclick="$( this ).explain( 'toggleView', 'query', this ); return false;"
|
||||||
|
onkeypress="return this.onclick( );">QUERY</a>
|
||||||
|
</li>
|
||||||
% }
|
% }
|
||||||
<li class="stats">
|
<li class="stats">
|
||||||
<a href="#stats"
|
<a href="#stats"
|
||||||
@ -592,6 +600,12 @@
|
|||||||
</div>
|
</div>
|
||||||
% }
|
% }
|
||||||
|
|
||||||
|
% if ( stash('query') ) {
|
||||||
|
<div class="result-query hidden">
|
||||||
|
<pre id="query"><code class="sql"><%= stash('query') %></code></pre>
|
||||||
|
</div>
|
||||||
|
% }
|
||||||
|
|
||||||
<div class="result-stats hidden">
|
<div class="result-stats hidden">
|
||||||
<h1>Per node type stats</h1>
|
<h1>Per node type stats</h1>
|
||||||
<table class="stats">
|
<table class="stats">
|
||||||
|
Reference in New Issue
Block a user