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 79     79   1283 use v5.18;
  79         337  
5 79     79   452 use strict;
  79         197  
  79         2458  
6 79     79   382 use warnings;
  79         149  
  79         4686  
7 79     79   453 use warnings qw( FATAL utf8 );
  79         168  
  79         8776  
8 79     79   496 use utf8;
  79         200  
  79         524  
9 79     79   3683 use open qw( :std :utf8 );
  79         254  
  79         482  
10 79     79   11473 use Unicode::Normalize qw( NFC );
  79         197  
  79         5124  
11 79     79   491 use Unicode::Collate;
  79         213  
  79         3979  
12 79     79   415 use Encode qw( decode );
  79         254  
  79         12901  
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 79     79   646 use Pg::Explain::Node;
  79         247  
  79         3348  
21 79     79   515 use Pg::Explain::Buffers;
  79         228  
  79         2922  
22 79     79   448 use Carp;
  79         149  
  79         193094  
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.9
31              
32             =cut
33              
34             our $VERSION = '2.9';
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 223     223 1 578 my $class = shift;
50 223         738 my $self = bless {}, $class;
51 223         788 return $self;
52             }
53              
54             =head2 explain
55              
56             Get/Set master explain object.
57              
58             =cut
59              
60 1216 100   1216 1 2558 sub explain { my $self = shift; $self->{ 'explain' } = $_[ 0 ] if 0 < scalar @_; return $self->{ 'explain' }; }
  1216         3615  
  1216         5760  
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 404     404 1 641 my $self = shift;
86 404         630 my $struct = shift;
87 404         819 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 578     578 1 1091 my $self = shift;
100 578         1007 my $struct = shift;
101              
102 578         2024 $struct = $self->normalize_node_struct( $struct );
103              
104 578         1458 my $use_type = $struct->{ 'Node Type' };
105 578 100       2137 if ( $use_type eq 'ModifyTable' ) {
    100          
106 6         16 $use_type = $struct->{ 'Operation' };
107 6 50       20 if ( $struct->{ 'Relation Name' } ) {
108 6         15 $use_type .= ' on ' . $struct->{ 'Relation Name' };
109 6 50 33     37 $use_type .= ' ' . $struct->{ 'Alias' } if ( $struct->{ 'Alias' } ) && ( $struct->{ 'Alias' } ne $struct->{ 'Relation Name' } );
110             }
111              
112             }
113             elsif ( $use_type eq 'Aggregate' ) {
114 48   50     194 my $strategy = $struct->{ 'Strategy' } || 'Plain';
115 48 100       160 $use_type = 'HashAggregate' if $strategy eq 'Hashed';
116 48 100       138 $use_type = 'GroupAggregate' if $strategy eq 'Sorted';
117 48 100       149 $use_type = 'MixedAggregate' if $strategy eq 'Mixed';
118             }
119 578 100 100     2903 if ( ( $struct->{ 'Scan Direction' } || '' ) eq 'Backward' ) {
120 6         19 $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 578         5049 'actual_loops' => $struct->{ 'Actual Loops' },
133             );
134 578         1935 $new_node->explain( $self->explain );
135              
136 578 50 66     2999 if ( ( defined $struct->{ 'Actual Startup Time' } )
137             && ( !$struct->{ 'Actual Loops' } ) )
138             {
139 0         0 $new_node->never_executed( 1 );
140             }
141              
142 578 100       6652 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 269         1737 '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         38 '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         214 '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         69 'cte_alias' => $struct->{ 'Alias' },
180             }
181             );
182             }
183             elsif ( $struct->{ 'Node Type' } eq 'Subquery Scan' ) {
184             $new_node->scan_on(
185             {
186 2         15 '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         21 'worktable_alias' => $struct->{ 'Alias' },
195             }
196             );
197             }
198              
199 578 100       1497 if ( $struct->{ 'Group Key' } ) {
200 9         21 my $key = join( ', ', @{ $struct->{ 'Group Key' } } );
  9         50  
201 9         61 $new_node->add_extra_info( 'Group Key: ' . $key );
202             }
203              
204 578 100       1303 if ( $struct->{ 'Grouping Sets' } ) {
205 2         6 for my $set ( @{ $struct->{ 'Grouping Sets' } } ) {
  2         8  
206 4         9 for my $hk ( @{ $set->{ 'Hash Keys' } } ) {
  4         12  
207 2         5 $new_node->add_extra_info( 'Hash Key: ' . join( ', ', @{ $hk } ) );
  2         14  
208             }
209 4         9 for my $gk ( @{ $set->{ 'Group Keys' } } ) {
  4         13  
210 2         7 $new_node->add_extra_info( 'Group Key: (' . join( ', ', @{ $gk } ) . ')' );
  2         13  
211             }
212             }
213             }
214              
215 578 100       1579 $new_node->add_extra_info( 'Workers Planned: ' . $struct->{ 'Workers Planned' } ) if $struct->{ 'Workers Planned' };
216 578 100       1490 if ( $struct->{ 'Workers Launched' } ) {
217 15         61 $new_node->workers_launched( $struct->{ 'Workers Launched' } );
218 15         51 $new_node->add_extra_info( 'Workers Launched: ' . $struct->{ 'Workers Launched' } );
219             }
220              
221 578 100       1486 if ( $struct->{ 'Recheck Cond' } ) {
222 6         45 $new_node->add_extra_info( 'Recheck Cond: ' . $struct->{ 'Recheck Cond' } );
223 6 100       27 if ( $struct->{ 'Rows Removed by Index Recheck' } ) {
224 3         12 $new_node->add_extra_info( 'Rows Removed by Index Recheck: ' . $struct->{ 'Rows Removed by Index Recheck' } );
225             }
226             }
227              
228 578 100       1276 if ( $struct->{ 'Join Filter' } ) {
229 3         18 $new_node->add_extra_info( 'Join Filter: ' . $struct->{ 'Join Filter' } );
230 3 50       12 if ( $struct->{ 'Rows Removed by Join Filter' } ) {
231 3         13 $new_node->add_extra_info( 'Rows Removed by Join Filter: ' . $struct->{ 'Rows Removed by Join Filter' } );
232             }
233             }
234              
235 578 100       1460 $new_node->add_extra_info( 'Index Cond: ' . $struct->{ 'Index Cond' } ) if $struct->{ 'Index Cond' };
236              
237 578 100       2409 if ( $struct->{ 'Filter' } ) {
238 209         953 $new_node->add_extra_info( 'Filter: ' . $struct->{ 'Filter' } );
239 209 50       613 if ( defined $struct->{ 'Rows Removed by Filter' } ) {
240 209         690 $new_node->add_extra_info( 'Rows Removed by Filter: ' . $struct->{ 'Rows Removed by Filter' } );
241             }
242             }
243              
244 578 100       1543 if ( $struct->{ 'Node Type' } eq 'Sort' ) {
245 12 100       60 if ( 'ARRAY' eq ref $struct->{ 'Sort Key' } ) {
246 9         23 $new_node->add_extra_info( 'Sort Key: ' . join( ', ', @{ $struct->{ 'Sort Key' } } ) );
  9         68  
247             }
248 12 100       47 if ( $struct->{ 'Sort Method' } ) {
249             $new_node->add_extra_info(
250             sprintf 'Sort Method: %s %s: %dkB',
251 9         81 $struct->{ 'Sort Method' }, $struct->{ 'Sort Space Type' }, $struct->{ 'Sort Space Used' }
252             );
253             }
254             }
255              
256 578 100       1367 $new_node->add_extra_info( 'Heap Fetches: ' . $struct->{ 'Heap Fetches' } ) if $struct->{ 'Heap Fetches' };
257              
258 578         1163 my @heap_blocks_info = ();
259 578         1203 for my $type ( qw( exact lossy ) ) {
260 1156         2411 my $key = ucfirst( $type ) . ' Heap Blocks';
261 1156 100       3104 next unless $struct->{ $key };
262 6         29 push @heap_blocks_info, sprintf '%s=%s', $type, $struct->{ $key };
263             }
264 578 100       1382 $new_node->add_extra_info( 'Heap Blocks: ' . join( ' ', @heap_blocks_info ) ) if 0 < scalar @heap_blocks_info;
265              
266 578         3404 my $buffers = Pg::Explain::Buffers->new( $struct );
267 578 100       3451 $new_node->buffers( $buffers ) if $buffers;
268              
269 578 100       1580 if ( $struct->{ 'Conflict Resolution' } ) {
270 3         17 $new_node->add_extra_info( 'Conflict Resolution: ' . $struct->{ 'Conflict Resolution' } );
271 3 50       15 if ( $struct->{ 'Conflict Arbiter Indexes' } ) {
272 3         6 $new_node->add_extra_info( 'Conflict Arbiter Indexes: ' . join( ', ', @{ $struct->{ 'Conflict Arbiter Indexes' } } ) );
  3         14  
273             }
274 3 50       12 if ( $struct->{ 'Conflict Filter' } ) {
275 3         14 $new_node->add_extra_info( 'Conflict Filter: ' . $struct->{ 'Conflict Filter' } );
276 3 50       10 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 578 100       1397 $new_node->add_extra_info( 'Tuples Inserted: ' . $struct->{ 'Tuples Inserted' } ) if defined $struct->{ 'Tuples Inserted' };
283              
284 578 100       1477 $new_node->add_extra_info( 'Conflicting Tuples: ' . $struct->{ 'Conflicting Tuples' } ) if defined $struct->{ 'Conflicting Tuples' };
285              
286 578 100       1544 if ( $struct->{ 'Plans' } ) {
287 346         628 my @plans;
288 346 50       1118 if ( 'HASH' eq ref $struct->{ 'Plans' } ) {
289 0         0 push @plans, $struct->{ 'Plans' };
290             }
291             else {
292 346         542 @plans = @{ $struct->{ 'Plans' } };
  346         944  
293             }
294 346         885 for my $subplan ( @plans ) {
295 355         1955 my $subnode = $self->make_node_from( $subplan );
296 355   50     1173 my $parent_relationship = $subplan->{ 'Parent Relationship' } // '';
297 355 100       1099 if ( $parent_relationship eq 'InitPlan' ) {
    100          
298 15 100       164 if ( $subplan->{ 'Subplan Name' } =~ m{ \A \s* CTE \s+ (\S+) \s* \z }xsm ) {
    50          
299 9         61 $new_node->add_cte( $1, $subnode );
300             }
301             elsif ( $subplan->{ 'Subplan Name' } =~ m{ \A InitPlan \s+ (\d+) \s+ \(returns \s+ ( .* )\) \z}xms ) {
302 6         68 $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         19 $new_node->add_subplan( $subnode );
316             }
317             else {
318 337         1337 $new_node->add_sub_node( $subnode );
319             }
320             }
321             }
322              
323 578         2047 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-2023 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