File Coverage

blib/lib/DBIx/NamedParams.pm
Criterion Covered Total %
statement 110 121 90.9
branch 22 34 64.7
condition 17 28 60.7
subroutine 22 23 95.6
pod 5 5 100.0
total 176 211 83.4


line stmt bran cond sub pod time code
1             package DBIx::NamedParams;
2              
3 3     3   271001 use 5.008001;
  3         79  
4 3     3   58 use strict;
  3         7  
  3         87  
5 3     3   26 use warnings;
  3         6  
  3         156  
6 3     3   636 use utf8;
  3         24  
  3         33  
7 3     3   1867 use Encode;
  3         32537  
  3         240  
8 3     3   32 use Carp qw( croak );
  3         4  
  3         217  
9 3     3   1472 use parent qw( DBI );
  3         1019  
  3         19  
10 3     3   41230 use DBI::Const::GetInfoType;
  3         18120  
  3         497  
11 3     3   1582 use Log::Dispatch;
  3         761360  
  3         200  
12 3     3   30 use POSIX qw( strftime );
  3         8  
  3         55  
13 3     3   3531 use Term::Encoding qw( term_encoding );
  3         1816  
  3         210  
14              
15 3     3   1366 use version 0.77; our $VERSION = version->declare("v0.0.9");
  3         6019  
  3         23  
16              
17             our $KeepBindingIfNoKey = 0;
18              
19             my $_default_log_filename = $ENV{'HOME'} || $ENV{'USERPROFILE'} || $ENV{'TMP'} || '/tmp';
20             $_default_log_filename =~ s#\\#/#g;
21             $_default_log_filename .= '/DBIx-NamedParams.log';
22              
23             my %_SQL_TypeRefs = ();
24             my %_SQL_TypeInvs = ();
25             my $_SQL_Types = '';
26             my @_NamedParams = ();
27             my $_index;
28             my $_log = undef;
29              
30             sub import {
31 3     3   120 DBI->import();
32 3         15 *{DBI::db::driver_typename_map} = \&driver_typename_map;
33 3         7 *{DBI::db::prepare_ex} = \&prepare_ex;
34 3         11 *{DBI::st::bind_param_ex} = \&bind_param_ex;
35 3         11 _init();
36             }
37              
38             sub _init {
39 3     3   5 foreach ( @{ $DBI::EXPORT_TAGS{sql_types} } ) {
  3         16  
40 174         276 my $refFunc = \&{"DBI::$_"};
  174         462  
41 174 50       613 if (/^SQL_(.*)$/i) {
42 174         223 $_SQL_TypeRefs{$1} = &{$refFunc};
  174         518  
43 174         232 $_SQL_TypeInvs{ &{$refFunc} } = $1;
  174         619  
44             }
45             }
46 3         26 $_SQL_Types = all_sql_types();
47             }
48              
49             sub _thisFuncName {
50 13     13   176 ( caller(1) )[3] =~ /([^:]+)$/;
51 13         64 return $1;
52             }
53              
54             sub debug_log {
55 0   0 0 1 0 my $filename = shift || $_default_log_filename;
56 0         0 $_log = Log::Dispatch->new(
57             outputs => [
58             [ 'File',
59             min_level => 'debug',
60             filename => encode( term_encoding, $filename ),
61             binmode => ":utf8",
62             permissions => 0666,
63             newline => 1,
64             ],
65             ],
66             );
67 0         0 $_log->info( _thisFuncName(), strftime( "%Y-%m-%d %H:%M:%S", localtime ) );
68             }
69              
70             sub all_sql_types {
71             return wantarray
72 4 100   4 1 2614 ? sort( keys(%_SQL_TypeRefs) )
73             : join( "|", sort( keys(%_SQL_TypeRefs) ) );
74             }
75              
76             sub driver_typename_map {
77 1     1 1 56409 my $self = shift;
78             my %map = map {
79 1         25 my $datatype = $_->{'SQL_DATA_TYPE'} # MS SQL Server
80             || $_->{'SQL_DATATYPE'} # MySQL
81 5   66     435 || $_->{'DATA_TYPE'}; # SQLite
82 5   100     32 ( $_->{'TYPE_NAME'} || '' ) => $_SQL_TypeInvs{$datatype} || 'WVARCHAR';
      50        
83             } $self->type_info();
84 1 50       21 if ( $self->get_info( $GetInfoType{'SQL_DBMS_NAME'} ) eq 'Microsoft SQL Server' ) {
85 0         0 $map{'datetime'} = 'WVARCHAR';
86 0         0 $map{'smalldatetime'} = 'WVARCHAR';
87             }
88 1         2075 return %map;
89             }
90              
91             sub prepare_ex {
92 7     7 1 29127 my ( $self, $sqlex, $refHash ) = @_;
93 7         17 my $ret = undef;
94 7   66     32 my $validHash = defined($refHash) && ref($refHash) eq 'HASH';
95 7 100       341 if ( $sqlex =~ /\:([\w]+)\+-($_SQL_Types)\b/ ) {
96 2 50       9 if ($validHash) {
97 2         204 $sqlex =~ s/\:([\w]+)\+-($_SQL_Types)\b/_parse_ex1($refHash,$1,$2);/ge;
  2         10  
98             } else {
99 0         0 croak("prepare_ex need a hash reference when SQL is variable length.");
100             }
101             }
102 7         28 @_NamedParams = ();
103 7         14 $_index = 1;
104 7         290 $sqlex =~ s/\:([\w]+)(?:\{(\d+)\})?-($_SQL_Types)\b/_parse_ex2($1,$2,$3);/ge;
  10         32  
105 7 50       19 if ($_log) {
106 0         0 $_log->info( _thisFuncName(), 'sql_raw', "{{$sqlex}}" );
107             }
108 7 50       64 $ret = $self->prepare($sqlex) or croak($DBI::errstr);
109 7 100       741 if ($validHash) {
110 2         8 $ret->bind_param_ex($refHash);
111             }
112 7         58 return $ret;
113             }
114              
115             sub _parse_ex1 {
116 2     2   13 my ( $refHash, $name, $type ) = @_;
117 2         5 my $numOfArray = 0;
118 2 50       9 if ( ref( $refHash->{$name} ) eq 'ARRAY' ) {
119 2         4 $numOfArray = scalar( @{ $refHash->{$name} } );
  2         5  
120             } else {
121 0         0 croak("Must be array: ${name}");
122             }
123 2         14 return ":${name}{${numOfArray}}-${type}";
124             }
125              
126             sub _parse_ex2 {
127 10   50 10   32 my $name = shift || '';
128 10   100     38 my $repeat = shift || 0;
129 10   50     27 my $type = shift || '';
130 10         26 my $ret = '';
131              
132 10 50       21 if ($_log) {
133 0 0       0 $_log->info( _thisFuncName(), "[$_index]", "\"$name\"",
134             ( !$repeat ) ? "scalar" : "array[$repeat]", $type );
135             }
136 10 100       24 if ( !$repeat ) { # scalar
137             $_NamedParams[ $_index++ ] = {
138             Name => $name,
139 7         28 Type => $_SQL_TypeRefs{$type},
140             Array => -1,
141             };
142 7         13 $ret = '?';
143             } else { # array
144 3         12 for ( my $i = 0; $i < $repeat; ++$i ) {
145             $_NamedParams[ $_index++ ] = {
146             Name => $name,
147 10         38 Type => $_SQL_TypeRefs{$type},
148             Array => $i,
149             };
150             }
151 3         11 $ret = substr( '?,' x $repeat, 0, -1 );
152             }
153 10         43 return $ret;
154             }
155              
156             sub bind_param_ex {
157 3     3   3879 no warnings 'uninitialized';
  3         7  
  3         968  
158 13     13 1 2019 my ( $self, $refHash ) = @_;
159 13 50 33     83 if ( !defined($refHash) || ref($refHash) ne 'HASH' ) {
160 0         0 croak("bind_param_ex need a hash reference.");
161             }
162 13         34 my $thisFunc = _thisFuncName();
163 13         45 for ( my $i = 1; $i < @_NamedParams; ++$i ) {
164 45         74 my $param = $_NamedParams[$i];
165 45 100 100     140 if ( $KeepBindingIfNoKey && !exists( $refHash->{ $param->{'Name'} } ) ) {
166 14         31 next;
167             }
168 31         45 my $idx = $param->{'Array'};
169 31         88 my $value1 = $refHash->{ $param->{'Name'} };
170 31 100 66     92 my $value2
171             = ( $idx < 0 || ref($value1) ne 'ARRAY' )
172             ? $value1
173             : $value1->[$idx];
174 31         48 my $datatype = $param->{'Type'};
175 31 50       57 if ($_log) {
176 0         0 $_log->info( $thisFunc, "[$i]", "\"$value2\"", $_SQL_TypeInvs{$datatype} );
177             }
178 31 50       237 $self->bind_param( $i, $value2, { TYPE => $datatype } )
179             or croak($DBI::errstr);
180             }
181 13         58 return $self;
182             }
183              
184             1;
185             __END__
186              
187             =encoding utf-8
188              
189             =head1 NAME
190              
191             DBIx::NamedParams - use named parameters instead of '?'
192              
193             =head1 SYNOPSIS
194              
195             This module allows you to use named parameters as the placeholders instead of '?'.
196              
197             use DBIx::NamedParams;
198              
199             # Connect DB
200             my $dbh = DBI->connect( ... ) or die($DBI::errstr);
201              
202             # Bind scalar
203             # :<Name>-<Type>
204             my $sql_insert = qq{
205             INSERT INTO `Users` ( `Name`, `Status` ) VALUES ( :Name-VARCHAR, :State-INTEGER );
206             };
207             my $sth_insert = $dbh->prepare_ex( $sql_insert ) or die($DBI::errstr);
208             $sth_insert->bind_param_ex( { 'Name' => 'Rio', 'State' => 1, } ) or die($DBI::errstr);
209             my $rv = $sth_insert->execute() or die($DBI::errstr);
210              
211             # Bind fixed array
212             # :<Name>{Number}-<Type>
213             my $sql_select1 = qq{
214             SELECT `ID`, `Name`, `Status`
215             FROM `Users`
216             WHERE `Status` in ( :State{4}-INTEGER );
217             };
218             my $sth_select1 = $dbh->prepare_ex( $sql_select1 ) or die($DBI::errstr);
219             $sth_select1->bind_param_ex( { 'State' => [ 1,2,4,8 ], } ) or die($DBI::errstr);
220             my $rv = $sth_select1->execute() or die($DBI::errstr);
221              
222             # Bind variable array
223             # :<Name>+-<Type>
224             my $sql_select2 = qq{
225             SELECT `ID`, `Name`, `Status`
226             FROM `Users`
227             WHERE `Status` in ( :State+-INTEGER );
228             };
229             my $sth_select2 = $dbh->prepare_ex( $sql_select2, { 'State' => [ 1,2,4,8 ], } )
230             or die($DBI::errstr);
231             my $rv = $sth_select2->execute() or die($DBI::errstr);
232              
233             =head1 DESCRIPTION
234              
235             DBIx::NamedParams helps binding SQL parameters.
236              
237             =head1 FLAGS
238              
239             =head2 $DBIx::NamedParams::KeepBindingIfNoKey
240              
241             In C<bind_param_ex()>, this flag controls the behavior when the hash reference doesn't have the key
242             in the SQL statement.
243              
244             Defaults to false. The placeholders according to the missing keys are set to C<undef>.
245             All of the placeholders have to be set at once.
246              
247             Setting this to a true value, the placeholders according to the missing keys are kept.
248             You can set some placeholders at first, and set other placeholders later.
249             If you want to set a placeholder to null, you have to set C<undef> explicitly.
250              
251             =head1 METHODS
252              
253             =head2 DBIx::NamedParams Class Methods
254              
255             =head3 all_sql_types
256              
257             Returns the all SQL data types defined in L<DBI> .
258              
259             my @types = DBIx::NamedParams::all_sql_types();
260              
261             =head3 debug_log
262              
263             Writes the parsed SQL statement and the values at the parameter positions into the log file.
264             When omitting the filename, creates the log file in the home directory.
265              
266             DBIx::NamedParams::debug_log( '/tmp/testNamedParams.log' );
267              
268             =head2 Database Handle Methods
269              
270             =head3 driver_typename_map
271              
272             Returns the hash from the driver type names to the DBI typenames.
273              
274             my %map = $dbh->driver_typename_map();
275              
276             =head3 prepare_ex
277              
278             Prepares a statement for later execution by the database engine and returns a reference to a statement handle object.
279             When the SQL statement has the variable array C<:E<lt>NameE<gt>+-E<lt>TypeE<gt>>, the hash reference as the second argument is mandatory.
280             When the SQL statement doesn't have the variable array C<:E<lt>NameE<gt>+-E<lt>TypeE<gt>>, the hash reference as the second argument is optional.
281              
282             my $sth = $dbh->prepare_ex( $statement, $hashref ) or die($DBI::errstr);
283              
284             =head2 Database Handle Methods
285              
286             =head3 bind_param_ex
287              
288             Binds each parameters at once according to the hash reference.
289             The hash reference should have the keys that are same names to the parameter names in the SQL statement.
290             When the hash reference doesn't have the key that is same to the parameter name, the parameter is not set.
291              
292             $sth->bind_param_ex( $hashref ) or die($DBI::errstr);
293              
294             =head1 SEE ALSO
295              
296             =head2 Similar modules
297              
298             L<Tao::DBI>
299              
300             L<DBIx::NamedBinding>
301              
302             =head2 DBD informations
303              
304             L<SQLite Keywords|https://www.sqlite.org/lang_keywords.html> explains how to quote the identifier.
305              
306             =head1 LICENSE
307              
308             Copyright (C) TakeAsh.
309              
310             This library is free software; you can redistribute it and/or modify
311             it under the same terms as Perl itself.
312              
313             =head1 AUTHOR
314              
315             L<TakeAsh|https://github.com/TakeAsh/>
316              
317             =cut
318