File Coverage

blib/lib/Pg/SQL/PrettyPrinter.pm
Criterion Covered Total %
statement 74 90 82.2
branch 20 28 71.4
condition n/a
subroutine 20 21 95.2
pod 5 5 100.0
total 119 144 82.6


line stmt bran cond sub pod time code
1             package Pg::SQL::PrettyPrinter;
2              
3             # UTF8 boilerplace, per http://stackoverflow.com/questions/6162484/why-does-modern-perl-avoid-utf-8-by-default/
4 16     16   1732292 use v5.26;
  16         56  
5 16     16   70 use strict;
  16         27  
  16         437  
6 16     16   66 use warnings;
  16         53  
  16         823  
7 16     16   73 use warnings qw( FATAL utf8 );
  16         32  
  16         860  
8 16     16   7149 use utf8;
  16         4125  
  16         83  
9 16     16   7573 use open qw( :std :utf8 );
  16         19524  
  16         131  
10 16     16   9788 use Unicode::Normalize qw( NFC );
  16         52976  
  16         1366  
11 16     16   9599 use Unicode::Collate;
  16         136491  
  16         761  
12 16     16   8026 use Encode qw( decode );
  16         238775  
  16         2855  
13              
14             if ( grep /\P{ASCII}/ => @ARGV ) {
15             @ARGV = map { decode( 'UTF-8', $_ ) } @ARGV;
16             }
17              
18             # If there is __DATA__,then uncomment next line:
19             # binmode( DATA, ':encoding(UTF-8)' );
20             # UTF8 boilerplace, per http://stackoverflow.com/questions/6162484/why-does-modern-perl-avoid-utf-8-by-default/
21              
22             # Useful common code
23 16     16   7263 use autodie;
  16         236552  
  16         68  
24 16     16   86838 use Carp qw( carp croak confess cluck );
  16         27  
  16         1457  
25 16     16   9079 use English qw( -no_match_vars );
  16         41300  
  16         89  
26 16     16   13682 use Data::Dumper qw( Dumper );
  16         113719  
  16         3389  
27              
28             # give a full stack dump on any untrapped exceptions
29             local $SIG{ __DIE__ } = sub {
30             confess "Uncaught exception: @_" unless $^S;
31             };
32              
33             # now promote run-time warnings into stackdumped exceptions
34             # *unless* we're in an try block, in which
35             # case just generate a clucking stackdump instead
36             local $SIG{ __WARN__ } = sub {
37             if ( $^S ) { cluck "Trapped warning: @_" }
38             else { confess "Deadly warning: @_" }
39             };
40              
41             # Useful common code
42              
43 16     16   11124 use HTTP::Tiny;
  16         713339  
  16         926  
44 16     16   7850 use JSON::MaybeXS;
  16         190640  
  16         1233  
45 16     16   7576 use Pg::SQL::PrettyPrinter::Node;
  16         50  
  16         13190  
46              
47             our $VERSION = '0.12';
48              
49             sub new {
50 143     143 1 2373741 my ( $class, %args ) = @_;
51 143         376 my $self = bless {}, $class;
52              
53 143 100       563 croak( 'SQL query was not provided!' ) unless $args{ 'sql' };
54 142         465 $self->{ 'sql' } = $args{ 'sql' };
55              
56 142 100       523 if ( exists $args{ 'service' } ) {
    100          
57 3 100       15 croak( 'You should provide only one of service/struct!' ) if $args{ 'struct' };
58 2 50       26 croak( 'Invalid syntax for service!' ) unless $args{ 'service' } =~ m{
59             \A
60             http://
61             \d{1,3} (?: \. \d{1,3} ){3} # IP address for parse microservice
62             :
63             [1-9]\d+ # Port number for parse microservice
64             /
65             \z
66             }x;
67 0         0 $self->{ 'service' } = $args{ 'service' };
68             }
69             elsif ( exists $args{ 'struct' } ) {
70 138         554 $self->validate_struct( $args{ 'struct' } );
71 131         288 $self->{ 'struct' } = $args{ 'struct' };
72             }
73             else {
74 1         9 croak( 'You have to provide either service or struct!' );
75             }
76 131         366 return $self;
77             }
78              
79             sub validate_struct {
80 138     138 1 254 my ( $self, $struct ) = @_;
81 138 100       516 croak( 'Invalid parse struct!' ) unless 'HASH' eq ref $struct;
82 136 100       375 croak( 'Invalid parse struct (#2)!' ) unless $struct->{ 'version' };
83 134 100       329 croak( 'Invalid parse struct (#3)!' ) unless $struct->{ 'stmts' };
84 133 100       458 croak( 'Invalid parse struct (#4)!' ) unless 'ARRAY' eq ref $struct->{ 'stmts' };
85 132 100       186 croak( 'Invalid parse struct (#5)!' ) unless 0 < scalar @{ $struct->{ 'stmts' } };
  132         334  
86 131         229 return;
87             }
88              
89             sub parse {
90 131     131 1 463 my $self = shift;
91 131         393 $self->fetch_struct();
92 131         241 $self->{ 'statements' } = [ map { Pg::SQL::PrettyPrinter::Node->make_from( $_->{ 'stmt' } ) } @{ $self->{ 'struct' }->{ 'stmts' } } ];
  131         827  
  131         348  
93 131         322 return;
94             }
95              
96             sub remove_irrelevant {
97 0     0 1 0 my $self = shift;
98 0         0 my $q = $self->{ 'sql' };
99 0         0 $q =~ s{
100             \A # Beginning of sql
101             \s* # Eventual spacing, including new lines
102             [a-z0-9_]* # optional dbname
103             [=-]? # optional prompt type
104             [>#\$] # prompt final character, depending on user level, or common(ish) '$'
105             \s* # optional spaces
106             }{}x;
107 0         0 $self->{ 'sql' } = $q;
108             }
109              
110             sub fetch_struct {
111 131     131 1 201 my $self = shift;
112 131 50       350 return if $self->{ 'struct' };
113 0           $self->remove_irrelevant();
114 0           my $http = HTTP::Tiny->new( 'timeout' => 0.5 ); # There really isn't a reason why it should take longer than 0.3s
115 0           my $res = $http->post_form( $self->{ 'service' }, { 'q' => $self->{ 'sql' } } );
116 0 0         unless ( $res->{ 'success' } ) {
117 0 0         croak( 'Timeout while parsing' ) if $res->{ 'content' } =~ m{\ATimed out while waiting for socket};
118 0           croak( "Couldn't parse the queries! : " . Dumper( $res ) );
119             }
120 0           my $struct = decode_json( $res->{ 'content' } );
121 0 0         croak( "Parse error: " . $struct->{ 'error' } ) if exists $struct->{ 'error' };
122 0           $self->validate_struct( $struct );
123 0           $self->{ 'struct' } = $struct;
124 0           return;
125             }
126              
127             1;