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 73     73   979 use v5.18;
  73         258  
5 73     73   380 use strict;
  73         153  
  73         1485  
6 73     73   403 use warnings;
  73         153  
  73         2621  
7 73     73   569 use warnings qw( FATAL utf8 );
  73         223  
  73         2752  
8 73     73   437 use utf8;
  73         196  
  73         485  
9 73     73   2563 use open qw( :std :utf8 );
  73         189  
  73         461  
10 73     73   9687 use Unicode::Normalize qw( NFC );
  73         228  
  73         4287  
11 73     73   492 use Unicode::Collate;
  73         168  
  73         2240  
12 73     73   450 use Encode qw( decode );
  73         204  
  73         4453  
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 73     73   15361 use Pg::Explain::Node;
  73         161  
  73         2112  
21 73     73   426 use Pg::Explain::Buffers;
  73         149  
  73         2716  
22 73     73   444 use Carp;
  73         202  
  73         143147  
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.3
31              
32             =cut
33              
34             our $VERSION = '2.3';
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 508 my $class = shift;
50 211         554 my $self = bless {}, $class;
51 211         578 return $self;
52             }
53              
54             =head2 explain
55              
56             Get/Set master explain object.
57              
58             =cut
59              
60 1134 100   1134 1 1751 sub explain { my $self = shift; $self->{ 'explain' } = $_[ 0 ] if 0 < scalar @_; return $self->{ 'explain' }; }
  1134         2946  
  1134         4691  
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 656 my $self = shift;
86 368         556 my $struct = shift;
87 368         740 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 1156 my $self = shift;
100 541         973 my $struct = shift;
101              
102 541         1599 $struct = $self->normalize_node_struct( $struct );
103              
104 541         1170 my $use_type = $struct->{ 'Node Type' };
105 541 100       1904 if ( $use_type eq 'ModifyTable' ) {
    100          
106 6         12 $use_type = $struct->{ 'Operation' };
107 6 50       21 if ( $struct->{ 'Relation Name' } ) {
108 6         19 $use_type .= ' on ' . $struct->{ 'Relation Name' };
109 6 50 33     41 $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     123 my $strategy = $struct->{ 'Strategy' } || 'Plain';
115 32 100       102 $use_type = 'HashAggregate' if $strategy eq 'Hashed';
116 32 100       111 $use_type = 'GroupAggregate' if $strategy eq 'Sorted';
117 32 100       104 $use_type = 'MixedAggregate' if $strategy eq 'Mixed';
118             }
119 541 100 100     2343 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         4164 'actual_loops' => $struct->{ 'Actual Loops' },
133             );
134 541         1443 $new_node->explain( $self->explain );
135              
136 541 50 66     2531 if ( ( defined $struct->{ 'Actual Startup Time' } )
137             && ( !$struct->{ 'Actual Loops' } ) )
138             {
139 0         0 $new_node->never_executed( 1 );
140             }
141              
142 541 100       4305 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         1461 '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         37 'function_alias' => $struct->{ 'Alias' },
155             }
156             );
157             }
158             elsif ( $struct->{ 'Node Type' } eq 'Bitmap Index Scan' ) {
159             $new_node->scan_on(
160             {
161 6         28 '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         212 '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         54 'cte_alias' => $struct->{ 'Alias' },
180             }
181             );
182             }
183             elsif ( $struct->{ 'Node Type' } eq 'Subquery Scan' ) {
184             $new_node->scan_on(
185             {
186 2         9 '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         17 'worktable_alias' => $struct->{ 'Alias' },
195             }
196             );
197             }
198              
199 541 100       1301 if ( $struct->{ 'Group Key' } ) {
200 9         14 my $key = join( ', ', @{ $struct->{ 'Group Key' } } );
  9         44  
201 9         40 $new_node->add_extra_info( 'Group Key: ' . $key );
202             }
203              
204 541 100       1191 if ( $struct->{ 'Grouping Sets' } ) {
205 2         3 for my $set ( @{ $struct->{ 'Grouping Sets' } } ) {
  2         7  
206 4         6 for my $hk ( @{ $set->{ 'Hash Keys' } } ) {
  4         8  
207 2         5 $new_node->add_extra_info( 'Hash Key: ' . join( ', ', @{ $hk } ) );
  2         9  
208             }
209 4         8 for my $gk ( @{ $set->{ 'Group Keys' } } ) {
  4         13  
210 2         6 $new_node->add_extra_info( 'Group Key: (' . join( ', ', @{ $gk } ) . ')' );
  2         7  
211             }
212             }
213             }
214              
215 541 100       1163 $new_node->add_extra_info( 'Workers Planned: ' . $struct->{ 'Workers Planned' } ) if $struct->{ 'Workers Planned' };
216 541 100       1150 if ( $struct->{ 'Workers Launched' } ) {
217 6         32 $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       1489 if ( $struct->{ 'Recheck Cond' } ) {
222 6         39 $new_node->add_extra_info( 'Recheck Cond: ' . $struct->{ 'Recheck Cond' } );
223 6 100       28 if ( $struct->{ 'Rows Removed by Index Recheck' } ) {
224 3         20 $new_node->add_extra_info( 'Rows Removed by Index Recheck: ' . $struct->{ 'Rows Removed by Index Recheck' } );
225             }
226             }
227              
228 541 100       1107 if ( $struct->{ 'Join Filter' } ) {
229 3         19 $new_node->add_extra_info( 'Join Filter: ' . $struct->{ 'Join Filter' } );
230 3 50       13 if ( $struct->{ 'Rows Removed by Join Filter' } ) {
231 3         15 $new_node->add_extra_info( 'Rows Removed by Join Filter: ' . $struct->{ 'Rows Removed by Join Filter' } );
232             }
233             }
234              
235 541 100       1161 $new_node->add_extra_info( 'Index Cond: ' . $struct->{ 'Index Cond' } ) if $struct->{ 'Index Cond' };
236              
237 541 100       1154 if ( $struct->{ 'Filter' } ) {
238 205         1114 $new_node->add_extra_info( 'Filter: ' . $struct->{ 'Filter' } );
239 205 50       483 if ( defined $struct->{ 'Rows Removed by Filter' } ) {
240 205         761 $new_node->add_extra_info( 'Rows Removed by Filter: ' . $struct->{ 'Rows Removed by Filter' } );
241             }
242             }
243              
244 541 100       1296 if ( $struct->{ 'Node Type' } eq 'Sort' ) {
245 12 100       56 if ( 'ARRAY' eq ref $struct->{ 'Sort Key' } ) {
246 9         27 $new_node->add_extra_info( 'Sort Key: ' . join( ', ', @{ $struct->{ 'Sort Key' } } ) );
  9         77  
247             }
248 12 100       50 if ( $struct->{ 'Sort Method' } ) {
249             $new_node->add_extra_info(
250             sprintf 'Sort Method: %s %s: %dkB',
251 9         90 $struct->{ 'Sort Method' }, $struct->{ 'Sort Space Type' }, $struct->{ 'Sort Space Used' }
252             );
253             }
254             }
255              
256 541 100       1284 $new_node->add_extra_info( 'Heap Fetches: ' . $struct->{ 'Heap Fetches' } ) if $struct->{ 'Heap Fetches' };
257              
258 541         1043 my @heap_blocks_info = ();
259 541         1008 for my $type ( qw( exact lossy ) ) {
260 1082         2425 my $key = ucfirst( $type ) . ' Heap Blocks';
261 1082 100       2608 next unless $struct->{ $key };
262 6         41 push @heap_blocks_info, sprintf '%s=%s', $type, $struct->{ $key };
263             }
264 541 100       1384 $new_node->add_extra_info( 'Heap Blocks: ' . join( ' ', @heap_blocks_info ) ) if 0 < scalar @heap_blocks_info;
265              
266 541         2565 my $buffers = Pg::Explain::Buffers->new( $struct );
267 541 100       2601 $new_node->buffers( $buffers ) if $buffers;
268              
269 541 100       1319 if ( $struct->{ 'Conflict Resolution' } ) {
270 3         16 $new_node->add_extra_info( 'Conflict Resolution: ' . $struct->{ 'Conflict Resolution' } );
271 3 50       9 if ( $struct->{ 'Conflict Arbiter Indexes' } ) {
272 3         7 $new_node->add_extra_info( 'Conflict Arbiter Indexes: ' . join( ', ', @{ $struct->{ 'Conflict Arbiter Indexes' } } ) );
  3         13  
273             }
274 3 50       8 if ( $struct->{ 'Conflict Filter' } ) {
275 3         12 $new_node->add_extra_info( 'Conflict Filter: ' . $struct->{ 'Conflict Filter' } );
276 3 50       9 if ( defined $struct->{ 'Rows Removed by Conflict Filter' } ) {
277 3         13 $new_node->add_extra_info( 'Rows Removed by Conflict Filter: ' . $struct->{ 'Rows Removed by Conflict Filter' } );
278             }
279             }
280             }
281              
282 541 100       1317 $new_node->add_extra_info( 'Tuples Inserted: ' . $struct->{ 'Tuples Inserted' } ) if defined $struct->{ 'Tuples Inserted' };
283              
284 541 100       1249 $new_node->add_extra_info( 'Conflicting Tuples: ' . $struct->{ 'Conflicting Tuples' } ) if defined $struct->{ 'Conflicting Tuples' };
285              
286 541 100       1195 if ( $struct->{ 'Plans' } ) {
287 320         612 my @plans;
288 320 50       952 if ( 'HASH' eq ref $struct->{ 'Plans' } ) {
289 0         0 push @plans, $struct->{ 'Plans' };
290             }
291             else {
292 320         535 @plans = @{ $struct->{ 'Plans' } };
  320         789  
293             }
294 320         910 for my $subplan ( @plans ) {
295 330         1164 my $subnode = $self->make_node_from( $subplan );
296 330   50     957 my $parent_relationship = $subplan->{ 'Parent Relationship' } // '';
297 330 100       1098 if ( $parent_relationship eq 'InitPlan' ) {
    100          
298 15 100       180 if ( $subplan->{ 'Subplan Name' } =~ m{ \A \s* CTE \s+ (\S+) \s* \z }xsm ) {
    50          
299 9         39 $new_node->add_cte( $1, $subnode );
300             }
301             elsif ( $subplan->{ 'Subplan Name' } =~ m{ \A InitPlan \s+ (\d+) \s+ \(returns \s+ ( .* )\) \z}xms ) {
302 6         57 $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         16 $new_node->add_subplan( $subnode );
316             }
317             else {
318 312         980 $new_node->add_sub_node( $subnode );
319             }
320             }
321             }
322              
323 541         1601 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