File Coverage

blib/lib/Footprintless/Plugin/Database/PreparedStatementTemplate.pm
Criterion Covered Total %
statement 104 105 99.0
branch 46 56 82.1
condition 27 30 90.0
subroutine 15 15 100.0
pod 2 2 100.0
total 194 208 93.2


line stmt bran cond sub pod time code
1 1     1   94306 use strict;
  1         7  
  1         22  
2 1     1   4 use warnings;
  1         1  
  1         35  
3              
4             package Footprintless::Plugin::Database::PreparedStatementTemplate;
5             $Footprintless::Plugin::Database::PreparedStatementTemplate::VERSION = '1.06';
6 1     1   4 use Carp;
  1         1  
  1         39  
7 1     1   4 use Carp 'verbose';
  1         8  
  1         115  
8 1     1   5 use Data::Dumper;
  1         2  
  1         37  
9 1     1   4 use Log::Any;
  1         2  
  1         4  
10              
11             my $logger = Log::Any->get_logger();
12              
13             sub new {
14 11     11 1 14153 my $self = bless( {}, shift );
15 11         41 $self->_init(@_);
16             }
17              
18             sub _init {
19 11     11   37 my ( $self, $sql_template, %bindings ) = @_;
20             my @binding_keys =
21 11 50       37 sort { ( length($b) <=> length($a) ) || ( $a cmp $b ) }
  24         64  
22             keys(%bindings);
23 11         20 my @split_text;
24             my @index_to_key;
25             $self->{bindings} =
26 11         19 { map { $_ => _transform_binding( $_, $bindings{$_} ) } @binding_keys };
  29         50  
27 11         26 _dice( _remove_comments($sql_template), \@split_text, \@index_to_key, @binding_keys );
28 11         41 $self->{prepared_statement} = join( '?', @split_text );
29             $self->{parameter_bindings} =
30 11         18 [ map { $self->{bindings}->{$_} } @index_to_key ];
  39         66  
31              
32 11         18 my %used_keys = map { $_ => 1 } @index_to_key;
  39         76  
33 11         21 foreach my $unused_key ( grep { !$used_keys{$_} } @binding_keys ) {
  29         53  
34 5         23 $logger->warn("Template var [$unused_key] is never used!");
35 5         88 delete( $self->{bindings}->{$unused_key} );
36             }
37 11         50 return $self;
38             }
39              
40             sub _bind {
41 41     41   57 my ( $binding, $context ) = @_;
42 41 100       103 if ( defined( my $key = $binding->{key} ) ) {
    100          
    100          
43 6         25 eval { $binding->{value} = $context->$key() }
44 12 100       33 if ( !defined( $binding->{value} = $context->{$key} ) );
45             croak(
46             "Cannot bind template var [$binding->{template_key}] - property [$key] cannot be bound in context"
47 12 50       85 ) unless defined( $binding->{value} );
48             }
49             elsif ( defined( my $reference = $binding->{reference} ) ) {
50             croak("Cannot bind template var [$binding->{template_key}] - reference to undefined")
51 6 50       18 unless defined( $binding->{value} = $$reference );
52             }
53             elsif ( defined( my $code = $binding->{code} ) ) {
54             croak("Cannot bind template var [$binding->{template_key}] - code returns undefined")
55 10 50       17 unless defined( $binding->{value} = $code->() );
56             }
57             }
58              
59             sub _dice {
60 128     128   218 my ( $text, $split_text, $index_to_key, $key, @keys ) = @_;
61 128 100       192 if ( !$key ) {
62 50         121 push( @$split_text, $text );
63             }
64             else {
65 78         95 my $add_ix = 0;
66              
67             # We need at least one element with a blank string in split...
68 78 100       433 foreach ( $text ? split( /\Q$key\E/, $text, -1 ) : ('') ) {
69 117 100       229 push( @$index_to_key, $key ) if ( $add_ix++ );
70 117         173 _dice( $_, $split_text, $index_to_key, @keys );
71             }
72             }
73             }
74              
75             sub query {
76 16     16 1 62 my ( $self, $context ) = @_;
77 16         37 my $query = { sql => $self->{prepared_statement} };
78 16 50       22 if ( %{ $self->{bindings} } ) {
  16         34  
79 16         26 foreach ( values( %{ $self->{bindings} } ) ) { _bind( $_, $context ) }
  16         40  
  41         90  
80             $query->{parameters} =
81 16         25 [ map { $_->{value} } @{ $self->{parameter_bindings} } ];
  67         107  
  16         29  
82 16         27 foreach ( values( %{ $self->{bindings} } ) ) { _unbind( $_, $context ) }
  16         31  
  41         59  
83             }
84 16         95 return $query;
85             }
86              
87             sub _remove_comments {
88 11     11   24 my ($sql) = @_;
89 11         27 my $sql_out;
90 1 50   1   7 open( my $fh, '>', \$sql_out ) || croak("Cannot write to string!");
  1         2  
  1         6  
  11         109  
91 11         596 my ( $in_block_comment, $in_line_comment, $in_quote ) = ( 0, 0, 0 );
92 11         28 for ( my $ix = 0; $ix < length($sql); ++$ix ) {
93 1304 100       1949 if ($in_block_comment) {
    100          
94 278 100       516 if ( substr( $sql, $ix, 2 ) eq '*/' ) {
95 11         14 $in_block_comment = 0;
96 11         18 ++$ix;
97             }
98             }
99             elsif ($in_line_comment) {
100 253 100       482 if ( substr( $sql, $ix, 1 ) eq "\n" ) {
101 10         12 $in_line_comment = 0;
102 10         20 print $fh ("\n");
103             }
104             }
105             else {
106 773         1032 my $char = substr( $sql, $ix, 1 );
107 773 100 100     2742 if ( !$in_quote && $char eq '/' && substr( $sql, $ix + 1, 1 ) eq '*' ) {
    100 66        
      100        
      66        
108 11         15 $in_block_comment = 1;
109 11         17 ++$ix;
110             }
111             elsif ( !$in_quote && $char eq '-' && substr( $sql, $ix + 1, 1 ) eq '-' ) {
112 10         13 $in_line_comment = 1;
113 10         18 ++$ix;
114             }
115             else {
116 752 100       1161 $in_quote = !$in_quote if ( $char eq "'" );
117 752         1352 print $fh ($char);
118             }
119             }
120             }
121 11         23 close($fh);
122 11         52 return $sql_out;
123             }
124              
125             sub _transform_binding {
126 29     29   46 my ( $template_key, $binding ) = @_;
127 29         41 my $ref = ref($binding);
128 29         53 my $new_binding = { template_key => $template_key };
129 29 100 100     230 if ( my $key = ( ( !$ref && $binding ) || ( $ref eq 'HASH' && $binding->{key} ) ) ) {
    100 100        
    100 100        
    50 66        
130 6         7 $new_binding->{key} = $key;
131             }
132             elsif ( my $reference =
133             ( ( $ref eq 'SCALAR' && $binding ) || ( $ref eq 'HASH' && $binding->{reference} ) ) )
134             {
135 3 50       7 croak(
136             "Template var [$template_key] - 'reference' property of binding is not a 'SCALAR' ref"
137             ) unless ref($reference) eq 'SCALAR';
138 3         6 $new_binding->{reference} = $reference;
139             }
140             elsif ( $ref eq 'HASH' && defined( $binding->{value} ) ) {
141             croak("'Template var [$template_key] - value' property of binding is a ref")
142 15 50       28 if ref( $binding->{value} );
143 15         22 $new_binding->{value} = $binding->{value};
144             }
145             elsif ( my $code =
146             ( ( $ref eq 'CODE' && $binding ) || ( $ref eq 'HASH' && $binding->{code} ) ) )
147             {
148 5 50       12 croak("Template var [$template_key] - 'code' property of binding is not a 'CODE' ref")
149             unless ref($code) eq 'CODE';
150 5         8 $new_binding->{code} = $code;
151             }
152             else {
153 0         0 croak( "Template var [$template_key] - binding [%s] is invalid", Dumper($binding) );
154             }
155 29         70 return $new_binding;
156             }
157              
158             sub _unbind {
159 41     41   63 my ($binding) = @_;
160             delete( $binding->{value} )
161             if defined( $binding->{key} )
162             || defined( $binding->{reference} )
163 41 100 100     153 || defined( $binding->{code} );
      100        
164             }
165              
166             1;
167              
168             __END__