File Coverage

blib/lib/DBIx/AutoUpgrade/NativeStrings.pm
Criterion Covered Total %
statement 69 74 93.2
branch 20 32 62.5
condition 14 26 53.8
subroutine 13 15 86.6
pod 2 5 40.0
total 118 152 77.6


line stmt bran cond sub pod time code
1 1     1   26276 use 5.10.0;
  1         5  
2             package DBIx::AutoUpgrade::NativeStrings;
3 1     1   8 use utf8;
  1         2  
  1         8  
4 1     1   31 use strict;
  1         2  
  1         24  
5 1     1   6 use warnings;
  1         2  
  1         36  
6 1     1   7 use Scalar::Util qw/looks_like_number/;
  1         5  
  1         75  
7 1     1   13 use Encode qw/decode/;
  1         3  
  1         120  
8 1         1648 use DBI (),
9              
10 1     1   9 our $VERSION = 1.01;
  1         3  
11              
12             my @default_dbh_methods = qw/do
13             prepare
14             selectrow_array
15             selectrow_arrayref
16             selectrow_hashref
17             selectall_arrayref
18             selectall_array
19             selectall_hashref
20             selectcol_arrayref/;
21              
22             my @default_sth_methods = qw/bind_param
23             bind_param_array
24             execute
25             execute_array/;
26              
27             my @sql_string_types = (DBI::SQL_CHAR, DBI::SQL_VARCHAR, DBI::SQL_LONGVARCHAR, DBI::SQL_WLONGVARCHAR,
28             DBI::SQL_WVARCHAR, DBI::SQL_WCHAR, DBI::SQL_CLOB);
29              
30              
31             my %valid_options = (
32             # name expected reftype default value
33             # ==== ================ =============
34             native => ['NOREF' , 'default' ],
35             decode_check => ['NOREF' , ],
36             debug => ['CODE' , ],
37             dbh_methods => ['ARRAY' , \@default_dbh_methods ],
38             sth_methods => ['ARRAY' , \@default_sth_methods ],
39             bind_type_is_string => ['ARRAY' , \&default_bind_type_is_string],
40             );
41              
42              
43              
44             sub new {
45 3     3 1 424858 my ($class, %options) = @_;
46              
47             # build object internals, checking validity of input args and supplying default values
48 3         26 my $self = {};
49 3         60 while (my ($option, $details) = each %valid_options) {
50 18         77 my ($expected_reftype, $default_val) = @$details;
51 18         66 my $val = delete $options{$option};
52 18 50 50     112 !$val or (ref $val || 'NOREF') eq $expected_reftype
      66        
53             or die "$class->new(): '$option' should be a $expected_reftype";
54 18   100     127 $val //= $default_val;
55 18 100       163 $self->{$option} = $val if $val;
56             }
57              
58             # check that there are no remaining input args
59 3         21 my @invalid_options = keys %options;
60 3 50       19 die "$class->new(): invalid options : " . join " / ", @invalid_options if @invalid_options;
61              
62             # make sure that Encode::Locale is loaded if needed
63 3 50       23 require Encode::Locale if $self->{native} eq 'locale';
64              
65             # return object
66 3         25 bless $self, $class;
67             }
68              
69              
70             sub inject_callbacks {
71 3     3 1 36 my ($self, $dbh, @invalid_args) = @_;
72              
73             # check input args
74 3 50       62 $dbh->isa('DBI::db') or die '->inject_callbacks() : arg is not a DBI database handle';
75 3 50       21 !@invalid_args or die '->inject_callbacks() : too many args';
76              
77             # coderef to be installed as common callback for all methods. This is a closure on $self.
78             my $upgrade_string_args = sub {
79             # NOTES: - here there is no unpacking of @_ because DBI callbacks must work directly on @_
80             # - $_ is the name of the DBI method
81              
82             # for calls to bind_param() with an explicit bind type, some types should be left untouched (for ex. SQL_BLOB)
83 174 50 66 174   49741 return if $_ eq 'bind_param' && $_[3] && !$self->{bind_type_is_string}->($_[3]);
      33        
84              
85             # vars to be used in the loop
86 174         579 my $debug = $self->{debug}; # copy just for easier reference
87 174         563 my $debug_msg = "$_ callback";
88 13         62 my $do_upgrade = $self->{native} eq 'default' ? sub {utf8::upgrade($_[0])}
89 174 100       1154 : sub {$_[0] = decode($self->{native}, $_[0], $self->{decode_check})};
  26         170  
90              
91             # loop over members of @_; start only at 1 because $_[0] is the DBI handle
92             ARG:
93 174         775 foreach my $i (1 .. $#_) {
94              
95             # if arg is undef or empty string or 0, there is nothing to do
96 420 50       2225 next ARG if !$_[$i];
97              
98             # if arg is a scalar and needs upgrading, do it
99 420 100       1502 if (! ref $_[$i]) {
    100          
100 297 100       902 next ARG if dont_need_upgrade($_[$i]);
101 33 50       118 $debug->("$debug_msg: upgrading arg [$i] ($_[$i])") if $debug;
102 33         103 $do_upgrade->($_[$i]);
103             }
104              
105             # if arg is an arrayref (used by the *_array methods), upgrade strings in that array
106             elsif (ref $_[$i] eq 'ARRAY') {
107 18         55 for my $val (grep {!dont_need_upgrade($_)} @{$_[$i]}) {
  36         139  
  18         78  
108 6 50       31 $debug->("$debug_msg: upgrading string in array arg [$i] ($val)") if $debug;
109 6         54 $do_upgrade->($val);
110             }
111             }
112             }
113              
114 174         390189 return; # must end with an empty return (see L documentation)
115 3         103 };
116              
117             # now inject the callback for $dbh methods and for $sth methods
118 3   50     160 my $parent_callbacks = $dbh->{Callbacks} //= {};
119 3   50     55 my $child_callbacks = $parent_callbacks->{ChildCallbacks} //= {};
120 3         11 inject_callback($parent_callbacks, $_ => $upgrade_string_args) for @{$self->{dbh_methods}};
  3         50  
121 3         12 inject_callback($child_callbacks, $_ => $upgrade_string_args) for @{$self->{sth_methods}};
  3         37  
122             }
123              
124              
125             sub inject_callback {
126 39     39 0 156 my ($hash, $key, $coderef) = @_;
127              
128             # in case a previous callback was already installed, we replace it with a sub that combines both
129 39         205 my $previous_cb = $hash->{$key};
130 39 50   0   132 my $new_cb = $previous_cb ? sub {&$coderef; &$previous_cb} : $coderef;
  0         0  
  0         0  
131              
132 39         290 $hash->{$key} = $new_cb;
133             }
134              
135             sub dont_need_upgrade {
136 333     333 0 902 my $scalar = shift;
137             # no need to upgrade if ..
138 333   100     3963 return looks_like_number($scalar) # .. it's a number
139             || utf8::is_utf8($scalar) # .. it's already a utf8 string
140 1     1   10 || $scalar !~ /\P{ASCII}/; # .. it only contains ASCII chars
  1         4  
  1         31  
141             }
142              
143             sub default_bind_type_is_string {
144 0     0 0   my $bind_type = shift;
145              
146             # according to L, the bind type can be given either as a scalar or as a hashref with a TYPE key
147 0 0 0       $bind_type = $bind_type->{TYPE} if (ref $bind_type || '') eq 'HASH';
148              
149 0   0       return looks_like_number($bind_type) && grep {$bind_type == $_} @sql_string_types;
150             }
151              
152             1;
153              
154              
155             __END__