File Coverage

blib/lib/Pg/Explain/From.pm
Criterion Covered Total %
statement 143 148 96.6
branch 84 94 89.3
condition 7 12 58.3
subroutine 16 17 94.1
pod 5 5 100.0
total 255 276 92.3


line stmt bran cond sub pod time code
1             package Pg::Explain::From;
2              
3             # UTF8 boilerplace, per http://stackoverflow.com/questions/6162484/why-does-modern-perl-avoid-utf-8-by-default/
4 74     74   944 use v5.18;
  74         297  
5 74     74   381 use strict;
  74         160  
  74         1511  
6 74     74   330 use warnings;
  74         158  
  74         2550  
7 74     74   423 use warnings qw( FATAL utf8 );
  74         172  
  74         2584  
8 74     74   426 use utf8;
  74         204  
  74         492  
9 74     74   2359 use open qw( :std :utf8 );
  74         210  
  74         434  
10 74     74   9880 use Unicode::Normalize qw( NFC );
  74         194  
  74         3999  
11 74     74   506 use Unicode::Collate;
  74         208  
  74         2229  
12 74     74   423 use Encode qw( decode );
  74         178  
  74         4452  
13              
14             if ( grep /\P{ASCII}/ => @ARGV ) {
15             @ARGV = map { decode( 'UTF-8', $_ ) } @ARGV;
16             }
17              
18             # UTF8 boilerplace, per http://stackoverflow.com/questions/6162484/why-does-modern-perl-avoid-utf-8-by-default/
19              
20 74     74   15533 use Pg::Explain::Node;
  74         170  
  74         2076  
21 74     74   411 use Pg::Explain::Buffers;
  74         161  
  74         2591  
22 74     74   458 use Carp;
  74         196  
  74         143325  
23              
24             =head1 NAME
25              
26             Pg::Explain::From - Base class for parsers of non-text explain formats.
27              
28             =head1 VERSION
29              
30             Version 2.4
31              
32             =cut
33              
34             our $VERSION = '2.4';
35              
36             =head1 SYNOPSIS
37              
38             It's internal class to wrap some work. It should be used by Pg::Explain, and not directly.
39              
40             =head1 FUNCTIONS
41              
42             =head2 new
43              
44             Object constructor.
45              
46             =cut
47              
48             sub new {
49 211     211 1 498 my $class = shift;
50 211         636 my $self = bless {}, $class;
51 211         580 return $self;
52             }
53              
54             =head2 explain
55              
56             Get/Set master explain object.
57              
58             =cut
59              
60 1134 100   1134 1 1770 sub explain { my $self = shift; $self->{ 'explain' } = $_[ 0 ] if 0 < scalar @_; return $self->{ 'explain' }; }
  1134         2778  
  1134         4477  
61              
62             =head2 parse_source
63              
64             Function which parses actual plan, and constructs Pg::Explain::Node objects
65             which represent it.
66              
67             Returns Top node of query plan.
68              
69             =cut
70              
71             sub parse_source {
72 0     0 1 0 my $self = shift;
73 0         0 croak( 'This method ( parse_source ) should be overriden in child class!' );
74             }
75              
76             =head2 normalize_node_struct
77              
78             Simple function to let subclasses change the real keys that should be used when parsing structure.
79              
80             This is (currently) useful only for XML parser.
81              
82             =cut
83              
84             sub normalize_node_struct {
85 368     368 1 639 my $self = shift;
86 368         563 my $struct = shift;
87 368         692 return $struct;
88             }
89              
90             =head2 make_node_from
91              
92             Converts single node from structure obtained from source into Pg::Explain::Node class.
93              
94             Recurses when necessary to get subnodes.
95              
96             =cut
97              
98             sub make_node_from {
99 541     541 1 1109 my $self = shift;
100 541         872 my $struct = shift;
101              
102 541         1512 $struct = $self->normalize_node_struct( $struct );
103              
104 541         1137 my $use_type = $struct->{ 'Node Type' };
105 541 100       1756 if ( $use_type eq 'ModifyTable' ) {
    100          
106 6         19 $use_type = $struct->{ 'Operation' };
107 6 50       19 if ( $struct->{ 'Relation Name' } ) {
108 6         18 $use_type .= ' on ' . $struct->{ 'Relation Name' };
109 6 50 33     44 $use_type .= ' ' . $struct->{ 'Alias' } if ( $struct->{ 'Alias' } ) && ( $struct->{ 'Alias' } ne $struct->{ 'Relation Name' } );
110             }
111              
112             }
113             elsif ( $use_type eq 'Aggregate' ) {
114 32   50     112 my $strategy = $struct->{ 'Strategy' } || 'Plain';
115 32 100       103 $use_type = 'HashAggregate' if $strategy eq 'Hashed';
116 32 100       92 $use_type = 'GroupAggregate' if $strategy eq 'Sorted';
117 32 100       92 $use_type = 'MixedAggregate' if $strategy eq 'Mixed';
118             }
119 541 100 100     2239 if ( ( $struct->{ 'Scan Direction' } || '' ) eq 'Backward' ) {
120 6         15 $use_type .= ' Backward';
121             }
122              
123             my $new_node = Pg::Explain::Node->new(
124             'type' => $use_type,
125             'estimated_startup_cost' => $struct->{ 'Startup Cost' },
126             'estimated_total_cost' => $struct->{ 'Total Cost' },
127             'estimated_rows' => $struct->{ 'Plan Rows' },
128             'estimated_row_width' => $struct->{ 'Plan Width' },
129             'actual_time_first' => $struct->{ 'Actual Startup Time' },
130             'actual_time_last' => $struct->{ 'Actual Total Time' },
131             'actual_rows' => $struct->{ 'Actual Rows' },
132 541         3559 'actual_loops' => $struct->{ 'Actual Loops' },
133             );
134 541         1411 $new_node->explain( $self->explain );
135              
136 541 50 66     2405 if ( ( defined $struct->{ 'Actual Startup Time' } )
137             && ( !$struct->{ 'Actual Loops' } ) )
138             {
139 0         0 $new_node->never_executed( 1 );
140             }
141              
142 541 100       4005 if ( $struct->{ 'Node Type' } =~ m{\A(?:Seq Scan|Bitmap Heap Scan)$} ) {
    100          
    100          
    100          
    100          
    100          
    100          
143             $new_node->scan_on(
144             {
145             'table_name' => $struct->{ 'Relation Name' },
146 257         1319 'table_alias' => $struct->{ 'Alias' },
147             }
148             );
149             }
150             elsif ( $struct->{ 'Node Type' } eq 'Function Scan' ) {
151             $new_node->scan_on(
152             {
153             'function_name' => $struct->{ 'Function Name' },
154 6         33 'function_alias' => $struct->{ 'Alias' },
155             }
156             );
157             }
158             elsif ( $struct->{ 'Node Type' } eq 'Bitmap Index Scan' ) {
159             $new_node->scan_on(
160             {
161 6         29 'index_name' => $struct->{ 'Index Name' },
162             }
163             );
164              
165             }
166             elsif ( $struct->{ 'Node Type' } =~ m{\AIndex(?: Only)? Scan(?: Backward)?\z} ) {
167             $new_node->scan_on(
168             {
169             'table_name' => $struct->{ 'Relation Name' },
170             'table_alias' => $struct->{ 'Alias' },
171 24         160 'index_name' => $struct->{ 'Index Name' },
172             }
173             );
174             }
175             elsif ( $struct->{ 'Node Type' } eq 'CTE Scan' ) {
176             $new_node->scan_on(
177             {
178             'cte_name' => $struct->{ 'CTE Name' },
179 9         53 'cte_alias' => $struct->{ 'Alias' },
180             }
181             );
182             }
183             elsif ( $struct->{ 'Node Type' } eq 'Subquery Scan' ) {
184             $new_node->scan_on(
185             {
186 2         8 'subquery_name' => $struct->{ 'Alias' },
187             }
188             );
189             }
190             elsif ( $struct->{ 'Node Type' } eq 'WorkTable Scan' ) {
191             $new_node->scan_on(
192             {
193             'worktable_name' => $struct->{ 'CTE Name' },
194 3         27 'worktable_alias' => $struct->{ 'Alias' },
195             }
196             );
197             }
198              
199 541 100       1244 if ( $struct->{ 'Group Key' } ) {
200 9         15 my $key = join( ', ', @{ $struct->{ 'Group Key' } } );
  9         28  
201 9         37 $new_node->add_extra_info( 'Group Key: ' . $key );
202             }
203              
204 541 100       1130 if ( $struct->{ 'Grouping Sets' } ) {
205 2         3 for my $set ( @{ $struct->{ 'Grouping Sets' } } ) {
  2         6  
206 4         8 for my $hk ( @{ $set->{ 'Hash Keys' } } ) {
  4         7  
207 2         5 $new_node->add_extra_info( 'Hash Key: ' . join( ', ', @{ $hk } ) );
  2         10  
208             }
209 4         8 for my $gk ( @{ $set->{ 'Group Keys' } } ) {
  4         23  
210 2         5 $new_node->add_extra_info( 'Group Key: (' . join( ', ', @{ $gk } ) . ')' );
  2         7  
211             }
212             }
213             }
214              
215 541 100       1242 $new_node->add_extra_info( 'Workers Planned: ' . $struct->{ 'Workers Planned' } ) if $struct->{ 'Workers Planned' };
216 541 100       1094 if ( $struct->{ 'Workers Launched' } ) {
217 6         29 $new_node->workers_launched( $struct->{ 'Workers Launched' } );
218 6         25 $new_node->add_extra_info( 'Workers Launched: ' . $struct->{ 'Workers Launched' } );
219             }
220              
221 541 100       1144 if ( $struct->{ 'Recheck Cond' } ) {
222 6         36 $new_node->add_extra_info( 'Recheck Cond: ' . $struct->{ 'Recheck Cond' } );
223 6 100       22 if ( $struct->{ 'Rows Removed by Index Recheck' } ) {
224 3         13 $new_node->add_extra_info( 'Rows Removed by Index Recheck: ' . $struct->{ 'Rows Removed by Index Recheck' } );
225             }
226             }
227              
228 541 100       1103 if ( $struct->{ 'Join Filter' } ) {
229 3         16 $new_node->add_extra_info( 'Join Filter: ' . $struct->{ 'Join Filter' } );
230 3 50       36 if ( $struct->{ 'Rows Removed by Join Filter' } ) {
231 3         17 $new_node->add_extra_info( 'Rows Removed by Join Filter: ' . $struct->{ 'Rows Removed by Join Filter' } );
232             }
233             }
234              
235 541 100       1224 $new_node->add_extra_info( 'Index Cond: ' . $struct->{ 'Index Cond' } ) if $struct->{ 'Index Cond' };
236              
237 541 100       1126 if ( $struct->{ 'Filter' } ) {
238 205         1068 $new_node->add_extra_info( 'Filter: ' . $struct->{ 'Filter' } );
239 205 50       557 if ( defined $struct->{ 'Rows Removed by Filter' } ) {
240 205         673 $new_node->add_extra_info( 'Rows Removed by Filter: ' . $struct->{ 'Rows Removed by Filter' } );
241             }
242             }
243              
244 541 100       1283 if ( $struct->{ 'Node Type' } eq 'Sort' ) {
245 12 100       71 if ( 'ARRAY' eq ref $struct->{ 'Sort Key' } ) {
246 9         24 $new_node->add_extra_info( 'Sort Key: ' . join( ', ', @{ $struct->{ 'Sort Key' } } ) );
  9         57  
247             }
248 12 100       43 if ( $struct->{ 'Sort Method' } ) {
249             $new_node->add_extra_info(
250             sprintf 'Sort Method: %s %s: %dkB',
251 9         98 $struct->{ 'Sort Method' }, $struct->{ 'Sort Space Type' }, $struct->{ 'Sort Space Used' }
252             );
253             }
254             }
255              
256 541 100       1162 $new_node->add_extra_info( 'Heap Fetches: ' . $struct->{ 'Heap Fetches' } ) if $struct->{ 'Heap Fetches' };
257              
258 541         999 my @heap_blocks_info = ();
259 541         997 for my $type ( qw( exact lossy ) ) {
260 1082         2304 my $key = ucfirst( $type ) . ' Heap Blocks';
261 1082 100       2458 next unless $struct->{ $key };
262 6         42 push @heap_blocks_info, sprintf '%s=%s', $type, $struct->{ $key };
263             }
264 541 100       1257 $new_node->add_extra_info( 'Heap Blocks: ' . join( ' ', @heap_blocks_info ) ) if 0 < scalar @heap_blocks_info;
265              
266 541         2450 my $buffers = Pg::Explain::Buffers->new( $struct );
267 541 100       2440 $new_node->buffers( $buffers ) if $buffers;
268              
269 541 100       1267 if ( $struct->{ 'Conflict Resolution' } ) {
270 3         17 $new_node->add_extra_info( 'Conflict Resolution: ' . $struct->{ 'Conflict Resolution' } );
271 3 50       12 if ( $struct->{ 'Conflict Arbiter Indexes' } ) {
272 3         6 $new_node->add_extra_info( 'Conflict Arbiter Indexes: ' . join( ', ', @{ $struct->{ 'Conflict Arbiter Indexes' } } ) );
  3         18  
273             }
274 3 50       10 if ( $struct->{ 'Conflict Filter' } ) {
275 3         13 $new_node->add_extra_info( 'Conflict Filter: ' . $struct->{ 'Conflict Filter' } );
276 3 50       9 if ( defined $struct->{ 'Rows Removed by Conflict Filter' } ) {
277 3         12 $new_node->add_extra_info( 'Rows Removed by Conflict Filter: ' . $struct->{ 'Rows Removed by Conflict Filter' } );
278             }
279             }
280             }
281              
282 541 100       1204 $new_node->add_extra_info( 'Tuples Inserted: ' . $struct->{ 'Tuples Inserted' } ) if defined $struct->{ 'Tuples Inserted' };
283              
284 541 100       1106 $new_node->add_extra_info( 'Conflicting Tuples: ' . $struct->{ 'Conflicting Tuples' } ) if defined $struct->{ 'Conflicting Tuples' };
285              
286 541 100       1169 if ( $struct->{ 'Plans' } ) {
287 320         678 my @plans;
288 320 50       859 if ( 'HASH' eq ref $struct->{ 'Plans' } ) {
289 0         0 push @plans, $struct->{ 'Plans' };
290             }
291             else {
292 320         914 @plans = @{ $struct->{ 'Plans' } };
  320         779  
293             }
294 320         844 for my $subplan ( @plans ) {
295 330         1170 my $subnode = $self->make_node_from( $subplan );
296 330   50     907 my $parent_relationship = $subplan->{ 'Parent Relationship' } // '';
297 330 100       950 if ( $parent_relationship eq 'InitPlan' ) {
    100          
298 15 100       146 if ( $subplan->{ 'Subplan Name' } =~ m{ \A \s* CTE \s+ (\S+) \s* \z }xsm ) {
    50          
299 9         41 $new_node->add_cte( $1, $subnode );
300             }
301             elsif ( $subplan->{ 'Subplan Name' } =~ m{ \A InitPlan \s+ (\d+) \s+ \(returns \s+ ( .* )\) \z}xms ) {
302 6         56 $new_node->add_initplan(
303             $subnode,
304             {
305             'name' => $1,
306             'returns' => $2,
307             }
308             );
309             }
310             else {
311 0         0 $new_node->add_initplan( $subnode );
312             }
313             }
314             elsif ( $parent_relationship eq 'SubPlan' ) {
315 3         18 $new_node->add_subplan( $subnode );
316             }
317             else {
318 312         966 $new_node->add_sub_node( $subnode );
319             }
320             }
321             }
322              
323 541         1583 return $new_node;
324              
325             }
326              
327             =head1 AUTHOR
328              
329             hubert depesz lubaczewski, C << >>
330              
331             =head1 BUGS
332              
333             Please report any bugs or feature requests to C.
334              
335             =head1 SUPPORT
336              
337             You can find documentation for this module with the perldoc command.
338              
339             perldoc Pg::Explain
340              
341             =head1 COPYRIGHT & LICENSE
342              
343             Copyright 2008-2021 hubert depesz lubaczewski, all rights reserved.
344              
345             This program is free software; you can redistribute it and/or modify it
346             under the same terms as Perl itself.
347              
348             =cut
349              
350             1; # End of Pg::Explain::From