File Coverage

blib/lib/DBIx/NamedParams.pm
Criterion Covered Total %
statement 115 124 92.7
branch 24 34 70.5
condition 22 30 73.3
subroutine 23 24 95.8
pod 5 5 100.0
total 189 217 87.1


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