Release 0.80

Changes:
- Allow parsing of plans with COSTS disabled, per gripe from Marc
- Properly parse WorkTable Scans, per gripe from Ivan Vergiliev
parent b149fe82
Revision history for Pg-Explain
0.80 2019/06/18
- Allow parsing of plans with COSTS disabled.
per gripe from Marc
- Properly parse WorkTable Scans
per gripe from Ivan Vergiliev
0.79 2019/06/12
- Added parsing of "Planning time", "Execution time", and "Trigger
time"
......
Build.PL
Changes
MANIFEST This list of files
META.json
META.yml
README
lib/Pg/Explain.pm
lib/Pg/Explain/From.pm
lib/Pg/Explain/FromJSON.pm
......@@ -12,6 +8,10 @@ lib/Pg/Explain/FromXML.pm
lib/Pg/Explain/FromYAML.pm
lib/Pg/Explain/Node.pm
lib/Pg/Explain/StringAnonymizer.pm
MANIFEST This list of files
META.json
META.yml
README
t/00-load.t
t/01-pod.t
t/02-pod-coverage.t
......@@ -131,6 +131,12 @@ t/34-parallel-index-only-scan.t
t/35-sort-mode-and-buffers-from-json.t
t/36-extra-info.t
t/36-extra-info/plan
t/37-plan-without-costs.t
t/37-plan-without-costs/1.expect
t/37-plan-without-costs/1.plan
t/37-plan-without-costs/2.expect
t/37-plan-without-costs/2.plan
t/38-worktable.t
t/99-manifest.t
t/perlcriticrc
t/perltidyrc
......
......@@ -42,35 +42,35 @@
"provides" : {
"Pg::Explain" : {
"file" : "lib/Pg/Explain.pm",
"version" : "0.79"
"version" : "0.80"
},
"Pg::Explain::From" : {
"file" : "lib/Pg/Explain/From.pm",
"version" : "0.79"
"version" : "0.80"
},
"Pg::Explain::FromJSON" : {
"file" : "lib/Pg/Explain/FromJSON.pm",
"version" : "0.79"
"version" : "0.80"
},
"Pg::Explain::FromText" : {
"file" : "lib/Pg/Explain/FromText.pm",
"version" : "0.79"
"version" : "0.80"
},
"Pg::Explain::FromXML" : {
"file" : "lib/Pg/Explain/FromXML.pm",
"version" : "0.79"
"version" : "0.80"
},
"Pg::Explain::FromYAML" : {
"file" : "lib/Pg/Explain/FromYAML.pm",
"version" : "0.79"
"version" : "0.80"
},
"Pg::Explain::Node" : {
"file" : "lib/Pg/Explain/Node.pm",
"version" : "0.79"
"version" : "0.80"
},
"Pg::Explain::StringAnonymizer" : {
"file" : "lib/Pg/Explain/StringAnonymizer.pm",
"version" : "0.79"
"version" : "0.80"
}
},
"release_status" : "stable",
......@@ -79,6 +79,6 @@
"http://dev.perl.org/licenses/"
]
},
"version" : "0.79",
"version" : "0.80",
"x_serialization_backend" : "JSON::PP version 2.27300_01"
}
......@@ -19,28 +19,28 @@ name: Pg-Explain
provides:
Pg::Explain:
file: lib/Pg/Explain.pm
version: '0.79'
version: '0.80'
Pg::Explain::From:
file: lib/Pg/Explain/From.pm
version: '0.79'
version: '0.80'
Pg::Explain::FromJSON:
file: lib/Pg/Explain/FromJSON.pm
version: '0.79'
version: '0.80'
Pg::Explain::FromText:
file: lib/Pg/Explain/FromText.pm
version: '0.79'
version: '0.80'
Pg::Explain::FromXML:
file: lib/Pg/Explain/FromXML.pm
version: '0.79'
version: '0.80'
Pg::Explain::FromYAML:
file: lib/Pg/Explain/FromYAML.pm
version: '0.79'
version: '0.80'
Pg::Explain::Node:
file: lib/Pg/Explain/Node.pm
version: '0.79'
version: '0.80'
Pg::Explain::StringAnonymizer:
file: lib/Pg/Explain/StringAnonymizer.pm
version: '0.79'
version: '0.80'
requires:
Clone: '0'
Digest::SHA: '0'
......@@ -51,5 +51,5 @@ requires:
perl: '5.010'
resources:
license: http://dev.perl.org/licenses/
version: '0.79'
version: '0.80'
x_serialization_backend: 'CPAN::Meta::YAML version 0.018'
......@@ -11,11 +11,11 @@ Pg::Explain - Object approach at reading explain analyze output
=head1 VERSION
Version 0.79
Version 0.80
=cut
our $VERSION = '0.79';
our $VERSION = '0.80';
=head1 SYNOPSIS
......
......@@ -9,11 +9,11 @@ Pg::Explain::From - Base class for parsers of non-text explain formats.
=head1 VERSION
Version 0.79
Version 0.80
=cut
our $VERSION = '0.79';
our $VERSION = '0.80';
=head1 SYNOPSIS
......
......@@ -10,11 +10,11 @@ Pg::Explain::FromJSON - Parser for explains in JSON format
=head1 VERSION
Version 0.79
Version 0.80
=cut
our $VERSION = '0.79';
our $VERSION = '0.80';
=head1 SYNOPSIS
......
......@@ -9,11 +9,11 @@ Pg::Explain::FromText - Parser for text based explains
=head1 VERSION
Version 0.79
Version 0.80
=cut
our $VERSION = '0.79';
our $VERSION = '0.80';
=head1 SYNOPSIS
......@@ -53,6 +53,17 @@ sub parse_source {
my @lines = split /\r?\n/, $source;
my $costs_re = qr{ \( cost=(?<estimated_startup_cost>\d+\.\d+)\.\.(?<estimated_total_cost>\d+\.\d+) \s+ rows=(?<estimated_rows>\d+) \s+ width=(?<estimated_row_width>\d+) \) }xms;
my $analyze_re = qr{ \(
(?:
actual \s time=(?<actual_time_first>\d+\.\d+)\.\.(?<actual_time_last>\d+\.\d+) \s rows=(?<actual_rows>\d+) \s loops=(?<actual_loops>\d+)
|
actual \s rows=(?<actual_rows>\d+) \s loops=(?<actual_loops>\d+)
|
(?<never_executed> never \s+ executed )
)
\) }xms;
LINE:
for my $line ( @lines ) {
......@@ -65,19 +76,13 @@ sub parse_source {
(?<prefix>\s* -> \s* | \s* )
(?<type>\S.*?)
\s+
\( 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=(?<actual_time_first>\d+\.\d+)\.\.(?<actual_time_last>\d+\.\d+) \s rows=(?<actual_rows>\d+) \s loops=(?<actual_loops>\d+)
|
actual \s rows=(?<actual_rows>\d+) \s loops=(?<actual_loops>\d+)
|
(?<never_executed> never \s+ executed )
)
\)
)?
$costs_re \s+ $analyze_re
|
$costs_re
|
$analyze_re
)
\s*
\z
}xms
......
......@@ -10,11 +10,11 @@ Pg::Explain::FromXML - Parser for explains in XML format
=head1 VERSION
Version 0.79
Version 0.80
=cut
our $VERSION = '0.79';
our $VERSION = '0.80';
=head1 SYNOPSIS
......
......@@ -10,11 +10,11 @@ Pg::Explain::FromYAML - Parser for explains in YAML format
=head1 VERSION
Version 0.79
Version 0.80
=cut
our $VERSION = '0.79';
our $VERSION = '0.80';
=head1 SYNOPSIS
......
......@@ -12,11 +12,11 @@ Pg::Explain::Node - Class representing single node from query plan
=head1 VERSION
Version 0.79
Version 0.80
=cut
our $VERSION = '0.79';
our $VERSION = '0.80';
=head1 SYNOPSIS
......@@ -230,14 +230,14 @@ sub new {
else {
%args = @_;
}
croak( 'type has to be passed to constructor of explain node' ) unless defined $args{ 'type' };
@{ $self }{ keys %args } = values %args;
# Backfill costs if they are not given from plan
for my $key ( qw( estimated_rows estimated_row_width estimated_startup_cost estimated_total_cost ) ) {
$args{ $key } = 0 unless defined $args{ $key };
}
croak( 'estimated_rows has to be passed to constructor of explain node' ) unless defined $self->estimated_rows;
croak( 'estimated_row_width has to be passed to constructor of explain node' ) unless defined $self->estimated_row_width;
croak( 'estimated_startup_cost has to be passed to constructor of explain node' ) unless defined $self->estimated_startup_cost;
croak( 'estimated_total_cost has to be passed to constructor of explain node' ) unless defined $self->estimated_total_cost;
croak( 'type has to be passed to constructor of explain node' ) unless defined $self->type;
@{ $self }{ keys %args } = values %args;
if ( $self->type =~ m{ \A ( (?: Parallel \s+ )? (?: Seq \s Scan | Bitmap \s+ Heap \s+ Scan | Foreign \s+ Scan | Update | Insert | Delete ) ) \s on \s (\S+) (?: \s+ (\S+) ) ? \z }xms ) {
$self->type( $1 );
......@@ -263,6 +263,11 @@ sub new {
$self->scan_on( { 'cte_name' => $2, } );
$self->scan_on->{ 'cte_alias' } = $3 if defined $3;
}
elsif ( $self->type =~ m{ \A ( WorkTable \s Scan ) \s on \s (\S+) (?: \s+ (\S+) ) ? \z }xms ) {
$self->type( $1 );
$self->scan_on( { 'worktable_name' => $2, } );
$self->scan_on->{ 'worktable_alias' } = $3 if defined $3;
}
elsif ( $self->type =~ m{ \A ( Function \s Scan ) \s on \s (\S+) (?: \s+ (\S+) )? \z }xms ) {
$self->type( $1 );
$self->scan_on( { 'function_name' => $2, } );
......
......@@ -11,11 +11,11 @@ Pg::Explain::StringAnonymizer - Class to anonymize sets of strings
=head1 VERSION
Version 0.79
Version 0.80
=cut
our $VERSION = '0.79';
our $VERSION = '0.80';
=head1 SYNOPSIS
......
#!perl
use Test::More;
use Test::Deep;
use Test::Exception;
use Data::Dumper;
use autodie;
use Pg::Explain;
opendir( my $dir, 't/37-plan-without-costs/' );
my %uniq = ();
my @tests = sort { $a <=> $b }
grep { !$uniq{ $_ }++ }
map { s/\..*//; $_ }
grep { /^\d+\.(?:expect|plan)$/ } readdir $dir;
closedir $dir;
plan 'tests' => 6 * scalar @tests;
for my $test ( @tests ) {
my $expected = get_expected_from_file( $test );
my $plan_file = 't/37-plan-without-costs/' . $test . '.plan';
my $explain = Pg::Explain->new( 'source_file' => $plan_file );
isa_ok( $explain, 'Pg::Explain', "Parsed plan $test" );
isa_ok( $explain->top_node, 'Pg::Explain::Node', "Parsed(2) plan $test" );
cmp_deeply( $explain->top_node->get_struct(), $expected, "Got proper data from parse of plan $test" );
my $reparsed = Pg::Explain->new( 'source' => $explain->as_text() );
isa_ok( $reparsed, 'Pg::Explain', "Reparsed plan $test" );
isa_ok( $reparsed->top_node, 'Pg::Explain::Node', "Reparsed(2) plan $test" );
cmp_deeply( $reparsed->top_node->get_struct(), $expected, "Got proper data from reparse of plan $test" );
}
exit;
sub get_expected_from_file {
my $test_no = shift;
my $filename = 't/37-plan-without-costs/' . $test_no . '.expect';
open my $fh, '<', $filename;
local $/ = undef;
my $expected_str = <$fh>;
close $fh;
my $expected = eval $expected_str;
die $@ if $@;
return $expected;
}
{
'actual_loops' => '1',
'actual_rows' => '427',
'actual_time_first' => '0.014',
'actual_time_last' => '0.141',
'estimated_rows' => 0,
'estimated_row_width' => 0,
'estimated_startup_cost' => 0,
'estimated_total_cost' => 0,
'is_analyzed' => 1,
'type' => 'Seq Scan',
'scan_on' => {
'table_name' => 'pg_class'
}
}
Seq Scan on pg_class (actual time=0.014..0.141 rows=427 loops=1)
{
'actual_loops' => '1',
'actual_rows' => '427',
'actual_time_first' => '0.014',
'actual_time_last' => '0.131',
'estimated_rows' => '427',
'estimated_row_width' => '371',
'estimated_startup_cost' => '0',
'estimated_total_cost' => '17.27',
'is_analyzed' => 1,
'type' => 'Seq Scan',
'scan_on' => {
'table_name' => 'pg_class'
}
}
Seq Scan on pg_class (cost=0.00..17.27 rows=427 width=371) (actual time=0.014..0.131 rows=427 loops=1)
#!perl
use Test::More;
use Test::Deep;
use Test::Exception;
use Data::Dumper;
use autodie;
use Pg::Explain;
my $plan = q{
CTE Scan on foo (cost=4.03..6.05 rows=101 width=4)
CTE foo
-> Recursive Union (cost=0.00..4.03 rows=101 width=4)
-> Result (cost=0.00..0.01 rows=1 width=4)
-> WorkTable Scan on foo foo_1 (cost=0.00..0.20 rows=10 width=4)
};
my $expected_struct = {
'estimated_rows' => '10',
'estimated_row_width' => '4',
'estimated_startup_cost' => '0',
'estimated_total_cost' => '0.2',
'is_analyzed' => 0,
'type' => 'WorkTable Scan',
'scan_on' => {
'worktable_alias' => 'foo_1',
'worktable_name' => 'foo'
}
};
plan 'tests' => 7;
my $explain = Pg::Explain->new( 'source' => $plan );
isa_ok( $explain, 'Pg::Explain' );
isa_ok( $explain->top_node, 'Pg::Explain::Node' );
is( $explain->top_node->type, 'CTE Scan', 'Properly got top node type' );
is( $explain->top_node->ctes->{ 'foo' }->type, 'Recursive Union', 'Properly got top->cte type' );
is( $explain->top_node->ctes->{ 'foo' }->sub_nodes->[ 0 ]->type, 'Result', 'Properly got top->cte->child(1) type' );
my $work_table = $explain->top_node->ctes->{ 'foo' }->sub_nodes->[ 1 ];
is( $work_table->type, 'WorkTable Scan', 'Properly got WorkTable type' );
cmp_deeply( $work_table->get_struct, $expected_struct, 'Structure of WorkTable is OK');
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