Allow parsing of plans without timing information, per request from Karl Bartel

parent e2dcdfc9
Revision history for Pg-Explain
0.70 2014/06/08
- Allow parsing of plans without timing information, per request from Karl Bartel
0.69 2014/06/08
- Anonymize CTE names, per gripe from Brian Dunavant
......
......@@ -113,6 +113,7 @@ t/22-anonymization-of-non-select-plans.t
t/23-anonymization-of-one-time-filters.t
t/24-anonymization-of-index-only-scans.t
t/25-anonymization-of-cte-names.t
t/26-explain-with-no-timing.t
t/99-manifest.t
t/perlcriticrc
t/perltidyrc
......
......@@ -4,7 +4,7 @@
"hubert depesz lubaczewski <depesz@depesz.com>"
],
"dynamic_config" : 1,
"generated_by" : "Module::Build version 0.4003, CPAN::Meta::Converter version 2.120921",
"generated_by" : "Module::Build version 0.421",
"license" : [
"perl_5"
],
......@@ -41,35 +41,35 @@
"provides" : {
"Pg::Explain" : {
"file" : "lib/Pg/Explain.pm",
"version" : "0.69"
"version" : "0.70"
},
"Pg::Explain::From" : {
"file" : "lib/Pg/Explain/From.pm",
"version" : "0.69"
"version" : "0.70"
},
"Pg::Explain::FromJSON" : {
"file" : "lib/Pg/Explain/FromJSON.pm",
"version" : "0.69"
"version" : "0.70"
},
"Pg::Explain::FromText" : {
"file" : "lib/Pg/Explain/FromText.pm",
"version" : "0.69"
"version" : "0.70"
},
"Pg::Explain::FromXML" : {
"file" : "lib/Pg/Explain/FromXML.pm",
"version" : "0.69"
"version" : "0.70"
},
"Pg::Explain::FromYAML" : {
"file" : "lib/Pg/Explain/FromYAML.pm",
"version" : "0.69"
"version" : "0.70"
},
"Pg::Explain::Node" : {
"file" : "lib/Pg/Explain/Node.pm",
"version" : "0.69"
"version" : "0.70"
},
"Pg::Explain::StringAnonymizer" : {
"file" : "lib/Pg/Explain/StringAnonymizer.pm",
"version" : "0.69"
"version" : "0.70"
}
},
"release_status" : "stable",
......@@ -78,5 +78,5 @@
"http://dev.perl.org/licenses/"
]
},
"version" : "0.69"
"version" : "0.70"
}
......@@ -3,51 +3,51 @@ abstract: 'Object approach at reading explain analyze output'
author:
- 'hubert depesz lubaczewski <depesz@depesz.com>'
build_requires:
Test::Deep: 0
Test::Exception: 0
Test::More: 0
autodie: 0
Test::Deep: '0'
Test::Exception: '0'
Test::More: '0'
autodie: '0'
configure_requires:
Module::Build: 0.38
Module::Build: '0.38'
dynamic_config: 1
generated_by: 'Module::Build version 0.4003, CPAN::Meta::Converter version 2.120921'
generated_by: 'Module::Build version 0.421, CPAN::Meta::Converter version 2.142690'
license: perl
meta-spec:
url: http://module-build.sourceforge.net/META-spec-v1.4.html
version: 1.4
version: '1.4'
name: Pg-Explain
provides:
Pg::Explain:
file: lib/Pg/Explain.pm
version: 0.69
version: '0.70'
Pg::Explain::From:
file: lib/Pg/Explain/From.pm
version: 0.69
version: '0.70'
Pg::Explain::FromJSON:
file: lib/Pg/Explain/FromJSON.pm
version: 0.69
version: '0.70'
Pg::Explain::FromText:
file: lib/Pg/Explain/FromText.pm
version: 0.69
version: '0.70'
Pg::Explain::FromXML:
file: lib/Pg/Explain/FromXML.pm
version: 0.69
version: '0.70'
Pg::Explain::FromYAML:
file: lib/Pg/Explain/FromYAML.pm
version: 0.69
version: '0.70'
Pg::Explain::Node:
file: lib/Pg/Explain/Node.pm
version: 0.69
version: '0.70'
Pg::Explain::StringAnonymizer:
file: lib/Pg/Explain/StringAnonymizer.pm
version: 0.69
version: '0.70'
requires:
Clone: 0
Digest::SHA1: 0
HOP::Lexer: 0
JSON: 0
XML::Simple: 0
YAML: 0
Clone: '0'
Digest::SHA1: '0'
HOP::Lexer: '0'
JSON: '0'
XML::Simple: '0'
YAML: '0'
resources:
license: http://dev.perl.org/licenses/
version: 0.69
version: '0.70'
......@@ -11,11 +11,11 @@ Pg::Explain - Object approach at reading explain analyze output
=head1 VERSION
Version 0.69
Version 0.70
=cut
our $VERSION = '0.69';
our $VERSION = '0.70';
=head1 SYNOPSIS
......
......@@ -9,11 +9,11 @@ Pg::Explain::From - Base class for parsers of non-text explain formats.
=head1 VERSION
Version 0.69
Version 0.70
=cut
our $VERSION = '0.69';
our $VERSION = '0.70';
=head1 SYNOPSIS
......
......@@ -10,11 +10,11 @@ Pg::Explain::FromJSON - Parser for explains in JSON format
=head1 VERSION
Version 0.69
Version 0.70
=cut
our $VERSION = '0.69';
our $VERSION = '0.70';
=head1 SYNOPSIS
......
......@@ -9,11 +9,11 @@ Pg::Explain::FromText - Parser for text based explains
=head1 VERSION
Version 0.69
Version 0.70
=cut
our $VERSION = '0.69';
our $VERSION = '0.70';
=head1 SYNOPSIS
......@@ -60,20 +60,21 @@ sub parse_source {
$line =~ s/"\z//;
if (
my @catch =
$line =~ m{
\A
(\s* -> \s* | \s* )
(\S.*?)
(?<prefix>\s* -> \s* | \s* )
(?<type>\S.*?)
\s+
\( cost=(\d+\.\d+)\.\.(\d+\.\d+) \s+ rows=(\d+) \s+ width=(\d+) \)
\( cost=(?<estimated_startup_cost>\d+\.\d+)\.\.(?<estimated_total_cost>\d+\.\d+) \s+ rows=(?<estimated_rows>\d+) \s+ width=(?<estimated_row_width>\d+) \)
(?:
\s+
\(
(?:
actual \s time=(\d+\.\d+)\.\.(\d+\.\d+) \s rows=(\d+) \s loops=(\d+)
actual \s time=(?<actual_time_first>\d+\.\d+)\.\.(?<actual_time_last>\d+\.\d+) \s rows=(?<actual_rows>\d+) \s loops=(?<actual_loops>\d+)
|
( never \s+ executed )
actual \s rows=(?<actual_rows>\d+) \s loops=(?<actual_loops>\d+)
|
(?<never_executed> never \s+ executed )
)
\)
)?
......@@ -82,37 +83,29 @@ sub parse_source {
}xms
)
{
my $new_node = Pg::Explain::Node->new(
'type' => $catch[ 1 ],
'estimated_startup_cost' => $catch[ 2 ],
'estimated_total_cost' => $catch[ 3 ],
'estimated_rows' => $catch[ 4 ],
'estimated_row_width' => $catch[ 5 ],
'actual_time_first' => $catch[ 6 ],
'actual_time_last' => $catch[ 7 ],
'actual_rows' => $catch[ 8 ],
'actual_loops' => $catch[ 9 ],
);
if ( defined $catch[ 10 ] && $catch[ 10 ] =~ m{never \s+ executed }xms ) {
my $new_node = Pg::Explain::Node->new( %+ );
if ( defined $+{ 'never_executed' } ) {
$new_node->actual_loops( 0 );
$new_node->never_executed( 1 );
}
my $element = { 'node' => $new_node, 'subelement-type' => 'subnode', };
my $prefix_length = length $+{ 'prefix' };
if ( 0 == scalar keys %element_at_depth ) {
$element_at_depth{ length $catch[ 0 ] } = $element;
$element_at_depth{ $prefix_length } = $element;
$top_node = $new_node;
next LINE;
}
my @existing_depths = sort { $a <=> $b } keys %element_at_depth;
for my $key ( grep { $_ >= length( $catch[ 0 ] ) } @existing_depths ) {
for my $key ( grep { $_ >= $prefix_length } @existing_depths ) {
delete $element_at_depth{ $key };
}
my $maximal_depth = ( sort { $b <=> $a } keys %element_at_depth )[ 0 ];
my $previous_element = $element_at_depth{ $maximal_depth };
$element_at_depth{ length $catch[ 0 ] } = $element;
$element_at_depth{ $prefix_length } = $element;
if ( $previous_element->{ 'subelement-type' } eq 'subnode' ) {
$previous_element->{ 'node' }->add_sub_node( $new_node );
......
......@@ -10,11 +10,11 @@ Pg::Explain::FromXML - Parser for explains in XML format
=head1 VERSION
Version 0.69
Version 0.70
=cut
our $VERSION = '0.69';
our $VERSION = '0.70';
=head1 SYNOPSIS
......
......@@ -10,11 +10,11 @@ Pg::Explain::FromYAML - Parser for explains in YAML format
=head1 VERSION
Version 0.69
Version 0.70
=cut
our $VERSION = '0.69';
our $VERSION = '0.70';
=head1 SYNOPSIS
......
......@@ -12,11 +12,11 @@ Pg::Explain::Node - Class representing single node from query plan
=head1 VERSION
Version 0.69
Version 0.70
=cut
our $VERSION = '0.69';
our $VERSION = '0.70';
=head1 SYNOPSIS
......@@ -544,12 +544,15 @@ sub as_text {
$heading_line .= sprintf ' (cost=%.3f..%.3f rows=%s width=%d)', $self->estimated_startup_cost, $self->estimated_total_cost, $self->estimated_rows, $self->estimated_row_width;
if ( $self->is_analyzed ) {
my $inner;
if ( 0 == $self->{ 'actual_loops' } ) {
if ( $self->never_executed ) {
$inner = 'never executed';
}
else {
elsif ( defined $self->actual_time_last ) {
$inner = sprintf 'actual time=%.3f..%.3f rows=%s loops=%d', $self->actual_time_first, $self->actual_time_last, $self->actual_rows, $self->actual_loops;
}
else {
$inner = sprintf 'actual rows=%s loops=%d', $self->actual_rows, $self->actual_loops;
}
$heading_line .= " ($inner)";
}
......
......@@ -11,11 +11,11 @@ Pg::Explain::StringAnonymizer - Class to anonymize sets of strings
=head1 VERSION
Version 0.69
Version 0.70
=cut
our $VERSION = '0.69';
our $VERSION = '0.70';
=head1 SYNOPSIS
......
#!perl
use Test::More;
use Test::Deep;
use Test::Exception;
use Data::Dumper;
use autodie;
use Pg::Explain;
my @plans = (
q{
Aggregate (cost=22.21..22.22 rows=1 width=0) (actual rows=1 loops=1)
-> Hash Join (cost=5.75..21.93 rows=111 width=0) (actual rows=113 loops=1)
Hash Cond: (pg_class.oid = pg_index.indrelid)
-> Index Only Scan using pg_class_oid_index on pg_class (cost=0.15..12.86 rows=314 width=4) (actual rows=319 loops=1)
Heap Fetches: 130
-> Hash (cost=4.22..4.22 rows=111 width=4) (actual rows=113 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 12kB
-> Seq Scan on pg_index (cost=0.00..4.22 rows=111 width=4) (actual rows=113 loops=1)
Filter: indisunique
Rows Removed by Filter: 11
},
q{
Result (cost=1.06..17.20 rows=314 width=201) (actual rows=0 loops=1)
One-Time Filter: ($0 > 1000)
InitPlan 1 (returns $0)
-> Aggregate (cost=1.05..1.06 rows=1 width=0) (actual rows=1 loops=1)
-> Seq Scan on pg_language (cost=0.00..1.04 rows=4 width=0) (actual rows=319 loops=1)
-> Seq Scan on pg_class (cost=0.00..16.14 rows=314 width=201) (never executed)
}
);
plan 'tests' => 4 * scalar @plans;
for my $plan_source ( @plans ) {
my $explain = Pg::Explain->new( 'source' => $plan_source );
isa_ok( $explain, 'Pg::Explain' );
isa_ok( $explain->top_node, 'Pg::Explain::Node' );
my $textual = $explain->as_text();
ok( $textual =~ m{\(actual rows=319 loops=1\)}, "Got actual data without timing" );
if ( $plan_source =~ m{never executed} ) {
ok( $textual =~ m{never executed}, "Plan is never executed" );
} else {
ok(1, 'placeholder test, to keep calculation of number of tests simple');
}
}
exit;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment