File Coverage

blib/lib/Pg/Explain/FromXML.pm
Criterion Covered Total %
statement 93 97 95.8
branch 37 44 84.0
condition 5 6 83.3
subroutine 16 16 100.0
pod 2 2 100.0
total 153 165 92.7


line stmt bran cond sub pod time code
1             package Pg::Explain::FromXML;
2              
3             # UTF8 boilerplace, per http://stackoverflow.com/questions/6162484/why-does-modern-perl-avoid-utf-8-by-default/
4 79     79   1075 use v5.18;
  79         328  
5 79     79   458 use strict;
  79         167  
  79         1980  
6 79     79   426 use warnings;
  79         185  
  79         5602  
7 79     79   443 use warnings qw( FATAL utf8 );
  79         137  
  79         4772  
8 79     79   551 use utf8;
  79         194  
  79         680  
9 79     79   3379 use open qw( :std :utf8 );
  79         209  
  79         593  
10 79     79   20143 use Unicode::Normalize qw( NFC );
  79         193  
  79         5170  
11 79     79   525 use Unicode::Collate;
  79         231  
  79         2644  
12 79     79   472 use Encode qw( decode );
  79         136  
  79         13874  
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   558 use base qw( Pg::Explain::From );
  79         155  
  79         11667  
21 79     79   71149 use XML::Simple;
  79         942913  
  79         714  
22 79     79   9346 use Carp;
  79         231  
  79         5339  
23 79     79   523 use Pg::Explain::JIT;
  79         165  
  79         2089  
24 79     79   425 use Pg::Explain::Buffers;
  79         174  
  79         98593  
25              
26             =head1 NAME
27              
28             Pg::Explain::FromXML - Parser for explains in XML format
29              
30             =head1 VERSION
31              
32             Version 2.9
33              
34             =cut
35              
36             our $VERSION = '2.9';
37              
38             =head1 SYNOPSIS
39              
40             It's internal class to wrap some work. It should be used by Pg::Explain, and not directly.
41              
42             =head1 FUNCTIONS
43              
44             =head2 normalize_node_struct
45              
46             XML structure is different than JSON/YAML (after parsing), so we need to normalize it.
47             =cut
48              
49             sub normalize_node_struct {
50 179     179 1 341 my $self = shift;
51 179         302 my $struct = shift;
52              
53 179         370 my @keys = keys %{ $struct };
  179         1545  
54 179         445 for my $key ( @keys ) {
55 2704         4004 my $new_key = $key;
56 2704         4370 $new_key =~ s{^I-O-(Read|Write)-Time$}{I/O $1 Time};
57 2704         6759 $new_key =~ s/-/ /g;
58 2704 100       9020 $struct->{ $new_key } = delete $struct->{ $key } if $key ne $new_key;
59             }
60              
61 179         411 my $subplans = [];
62 179 50 66     851 if ( ( $struct->{ 'Plans' } )
63             && ( $struct->{ 'Plans' }->{ 'Plan' } ) )
64             {
65 68 100       363 if ( 'HASH' eq ref $struct->{ 'Plans' }->{ 'Plan' } ) {
66 31         93 push @{ $subplans }, $struct->{ 'Plans' }->{ 'Plan' };
  31         147  
67             }
68             else {
69 37         94 $subplans = $struct->{ 'Plans' }->{ 'Plan' };
70             }
71             }
72 179         484 $struct->{ 'Plans' } = $subplans;
73              
74 179 100       580 if ( $struct->{ 'Group Key' } ) {
75 3         12 my $items = $struct->{ 'Group Key' }->{ 'Item' };
76 3 100       17 if ( 'ARRAY' eq ref $items ) {
77 1         3 $struct->{ 'Group Key' } = $items;
78             }
79             else {
80 2         11 $struct->{ 'Group Key' } = [ $items ];
81             }
82             }
83              
84 179 100       497 if ( $struct->{ 'Conflict Arbiter Indexes' } ) {
85 1         5 $struct->{ 'Conflict Arbiter Indexes' } = [ $struct->{ 'Conflict Arbiter Indexes' }->{ 'Item' } ];
86             }
87 179         926 return $struct;
88             }
89              
90             =head2 parse_source
91              
92             Function which parses actual plan, and constructs Pg::Explain::Node objects
93             which represent it.
94              
95             Returns Top node of query plan.
96              
97             =cut
98              
99             sub parse_source {
100 68     68 1 156 my $self = shift;
101 68         191 my $source = shift;
102              
103 68 50       1183 unless ( $source =~ s{\A .*? ^ \s* () \s* $}{$1}xms ) {
104 0         0 carp( 'Source does not match first s///' );
105 0         0 return;
106             }
107 68 50       7499 unless ( $source =~ s{^ \s* \s* $ .* \z}{}xms ) {
108 0         0 carp( 'Source does not match second s///' );
109 0         0 return;
110             }
111              
112 68         579 my $struct = XMLin( $source );
113              
114             # Need this to work around a bit different format from auto-explain module
115 68 100       2238771 $struct = $struct->{ 'Query' } if defined $struct->{ 'Query' };
116              
117 68         853 my $top_node = $self->make_node_from( $struct->{ 'Plan' } );
118              
119 68 100       453 if ( $struct->{ 'Planning' } ) {
    100          
120 5         19 $self->explain->planning_time( $struct->{ 'Planning' }->{ 'Planning-Time' } );
121 5         23 my $buffers = Pg::Explain::Buffers->new( $self->normalize_node_struct( $struct->{ 'Planning' } ) );
122 5 100       19 $self->explain->planning_buffers( $buffers ) if $buffers;
123             }
124             elsif ( $struct->{ 'Planning-Time' } ) {
125 48         230 $self->explain->planning_time( $struct->{ 'Planning-Time' } );
126             }
127 68 100       357 $self->explain->execution_time( $struct->{ 'Execution-Time' } ) if $struct->{ 'Execution-Time' };
128 68 100       282 $self->explain->total_runtime( $struct->{ 'Total-Runtime' } ) if $struct->{ 'Total-Runtime' };
129 68 100       2212 if ( $struct->{ 'Triggers' } ) {
130 60         130 for my $t ( @{ $struct->{ 'Triggers' }->{ 'Trigger' } } ) {
  60         328  
131 2         4 my $ts = {};
132 2 50       6 $ts->{ 'calls' } = $t->{ 'Calls' } if defined $t->{ 'Calls' };
133 2 50       6 $ts->{ 'time' } = $t->{ 'Time' } if defined $t->{ 'Time' };
134 2 50       5 $ts->{ 'relation' } = $t->{ 'Relation' } if defined $t->{ 'Relation' };
135 2 50       5 $ts->{ 'name' } = $t->{ 'Trigger-Name' } if defined $t->{ 'Trigger-Name' };
136 2         3 $self->explain->add_trigger_time( $ts );
137             }
138             }
139 68 100       300 $self->explain->jit( Pg::Explain::JIT->new( 'struct' => $struct->{ 'JIT' } ) ) if $struct->{ 'JIT' };
140              
141 68 100       229 $self->explain->query( $struct->{ 'Query-Text' } ) if $struct->{ 'Query-Text' };
142              
143 68 100 100     294 $self->explain->settings( $struct->{ 'Settings' } ) if ( $struct->{ 'Settings' } ) && ( 0 < scalar keys %{ $struct->{ 'Settings' } } );
  3         14  
144              
145 68         1157 return $top_node;
146             }
147              
148             =head1 AUTHOR
149              
150             hubert depesz lubaczewski, C<< >>
151              
152             =head1 BUGS
153              
154             Please report any bugs or feature requests to C.
155              
156             =head1 SUPPORT
157              
158             You can find documentation for this module with the perldoc command.
159              
160             perldoc Pg::Explain
161              
162             =head1 COPYRIGHT & LICENSE
163              
164             Copyright 2008-2023 hubert depesz lubaczewski, all rights reserved.
165              
166             This program is free software; you can redistribute it and/or modify it
167             under the same terms as Perl itself.
168              
169             =cut
170              
171             1; # End of Pg::Explain::FromXML