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 74     74   963 use v5.18;
  74         303  
5 74     74   422 use strict;
  74         186  
  74         1540  
6 74     74   391 use warnings;
  74         157  
  74         3127  
7 74     74   447 use warnings qw( FATAL utf8 );
  74         146  
  74         2530  
8 74     74   422 use utf8;
  74         168  
  74         446  
9 74     74   2560 use open qw( :std :utf8 );
  74         190  
  74         520  
10 74     74   10051 use Unicode::Normalize qw( NFC );
  74         210  
  74         4461  
11 74     74   558 use Unicode::Collate;
  74         167  
  74         2653  
12 74     74   444 use Encode qw( decode );
  74         228  
  74         4620  
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   16246 use base qw( Pg::Explain::From );
  74         228  
  74         8186  
21 74     74   61704 use XML::Simple;
  74         685508  
  74         544  
22 74     74   6303 use Carp;
  74         209  
  74         3732  
23 74     74   477 use Pg::Explain::JIT;
  74         196  
  74         1639  
24 74     74   396 use Pg::Explain::Buffers;
  74         168  
  74         69747  
25              
26             =head1 NAME
27              
28             Pg::Explain::FromXML - Parser for explains in XML format
29              
30             =head1 VERSION
31              
32             Version 2.4
33              
34             =cut
35              
36             our $VERSION = '2.4';
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 177     177 1 320 my $self = shift;
51 177         315 my $struct = shift;
52              
53 177         429 my @keys = keys %{ $struct };
  177         987  
54 177         439 for my $key ( @keys ) {
55 2661         3868 my $new_key = $key;
56 2661         4139 $new_key =~ s{^I-O-(Read|Write)-Time$}{I/O $1 Time};
57 2661         6349 $new_key =~ s/-/ /g;
58 2661 100       9268 $struct->{ $new_key } = delete $struct->{ $key } if $key ne $new_key;
59             }
60              
61 177         369 my $subplans = [];
62 177 50 66     797 if ( ( $struct->{ 'Plans' } )
63             && ( $struct->{ 'Plans' }->{ 'Plan' } ) )
64             {
65 68 100       297 if ( 'HASH' eq ref $struct->{ 'Plans' }->{ 'Plan' } ) {
66 31         71 push @{ $subplans }, $struct->{ 'Plans' }->{ 'Plan' };
  31         132  
67             }
68             else {
69 37         148 $subplans = $struct->{ 'Plans' }->{ 'Plan' };
70             }
71             }
72 177         457 $struct->{ 'Plans' } = $subplans;
73              
74 177 100       525 if ( $struct->{ 'Group Key' } ) {
75 3         10 my $items = $struct->{ 'Group Key' }->{ 'Item' };
76 3 100       10 if ( 'ARRAY' eq ref $items ) {
77 1         114 $struct->{ 'Group Key' } = $items;
78             }
79             else {
80 2         6 $struct->{ 'Group Key' } = [ $items ];
81             }
82             }
83              
84 177 100       420 if ( $struct->{ 'Conflict Arbiter Indexes' } ) {
85 1         4 $struct->{ 'Conflict Arbiter Indexes' } = [ $struct->{ 'Conflict Arbiter Indexes' }->{ 'Item' } ];
86             }
87 177         639 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 67     67 1 162 my $self = shift;
101 67         174 my $source = shift;
102              
103 67 50       1747 unless ( $source =~ s{\A .*? ^ \s* () \s* $}{$1}xms ) {
104 0         0 carp( 'Source does not match first s///' );
105 0         0 return;
106             }
107 67 50       6575 unless ( $source =~ s{^ \s* \s* $ .* \z}{}xms ) {
108 0         0 carp( 'Source does not match second s///' );
109 0         0 return;
110             }
111              
112 67         409 my $struct = XMLin( $source );
113              
114             # Need this to work around a bit different format from auto-explain module
115 67 100       3499185 $struct = $struct->{ 'Query' } if defined $struct->{ 'Query' };
116              
117 67         727 my $top_node = $self->make_node_from( $struct->{ 'Plan' } );
118              
119 67 100       401 if ( $struct->{ 'Planning' } ) {
    100          
120 4         20 $self->explain->planning_time( $struct->{ 'Planning' }->{ 'Planning-Time' } );
121 4         20 my $buffers = Pg::Explain::Buffers->new( $self->normalize_node_struct( $struct->{ 'Planning' } ) );
122 4 100       18 $self->explain->planning_buffers( $buffers ) if $buffers;
123             }
124             elsif ( $struct->{ 'Planning-Time' } ) {
125 48         158 $self->explain->planning_time( $struct->{ 'Planning-Time' } );
126             }
127 67 100       377 $self->explain->execution_time( $struct->{ 'Execution-Time' } ) if $struct->{ 'Execution-Time' };
128 67 100       238 $self->explain->total_runtime( $struct->{ 'Total-Runtime' } ) if $struct->{ 'Total-Runtime' };
129 67 100       216 if ( $struct->{ 'Triggers' } ) {
130 59         104 for my $t ( @{ $struct->{ 'Triggers' }->{ 'Trigger' } } ) {
  59         278  
131 2         3 my $ts = {};
132 2 50       10 $ts->{ 'calls' } = $t->{ 'Calls' } if defined $t->{ 'Calls' };
133 2 50       8 $ts->{ 'time' } = $t->{ 'Time' } if defined $t->{ 'Time' };
134 2 50       7 $ts->{ 'relation' } = $t->{ 'Relation' } if defined $t->{ 'Relation' };
135 2 50       10 $ts->{ 'name' } = $t->{ 'Trigger-Name' } if defined $t->{ 'Trigger-Name' };
136 2         5 $self->explain->add_trigger_time( $ts );
137             }
138             }
139 67 100       236 $self->explain->jit( Pg::Explain::JIT->new( 'struct' => $struct->{ 'JIT' } ) ) if $struct->{ 'JIT' };
140              
141 67 100       262 $self->explain->query( $struct->{ 'Query-Text' } ) if $struct->{ 'Query-Text' };
142              
143 67 100 100     269 $self->explain->settings( $struct->{ 'Settings' } ) if ( $struct->{ 'Settings' } ) && ( 0 < scalar keys %{ $struct->{ 'Settings' } } );
  3         15  
144              
145 67         823 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-2021 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