File Coverage

blib/lib/DBIx/Placeholder/Named.pm
Criterion Covered Total %
statement 60 69 86.9
branch 17 28 60.7
condition 4 9 44.4
subroutine 10 11 90.9
pod 2 2 100.0
total 93 119 78.1


line stmt bran cond sub pod time code
1             package DBIx::Placeholder::Named;
2              
3 7     7   56877 use warnings;
  7         16  
  7         267  
4 7     7   40 use strict;
  7         15  
  7         335  
5              
6 7     7   56 use base qw(DBI);
  7         10  
  7         25539  
7              
8             our $VERSION = '0.08';
9             our $PREFIX = ':';
10             our $SUFFIX = '';
11              
12             use constant {
13 7         3734 ATTR => 3,
14             OLD_DRIVER => 4,
15 7     7   121684 };
  7         16  
16              
17             sub connect {
18 4     4 1 3291 my ( $class, @args ) = @_;
19              
20 4         10 my ( $prefix, $suffix );
21              
22 4 50 33     53 if ( $args[ATTR] and ref( $args[ATTR] ) eq 'HASH' ) {
23 4 100       38 $prefix = delete $args[ATTR]{PlaceholderPrefix}
24             if ( exists $args[ATTR]{PlaceholderPrefix} );
25 4 100       23 $suffix = delete $args[ATTR]{PlaceholderSuffix}
26             if ( exists $args[ATTR]{PlaceholderSuffix} );
27             }
28              
29 4         45 my $self = $class->SUPER::connect(@args);
30              
31 4 100       86688 $self->{private_dbix_placeholder_named_info}{prefix} =
32             defined $prefix ? $prefix : ':';
33 4 100       39 $self->{private_dbix_placeholder_named_info}{suffix} =
34             defined $suffix ? $suffix : '';
35              
36 4         31 return $self;
37             }
38              
39             sub connect_cached {
40 0     0 1 0 my ( $class, @args ) = @_;
41              
42 0         0 my ( $prefix, $suffix );
43              
44 0 0 0     0 if ( $args[ATTR] and ref( $args[ATTR] ) eq 'HASH' ) {
45 0 0       0 $prefix = delete $args[ATTR]{PlaceholderPrefix}
46             if ( exists $args[ATTR]{PlaceholderPrefix} );
47 0 0       0 $suffix = delete $args[ATTR]{PlaceholderSuffix}
48             if ( exists $args[ATTR]{PlaceholderSuffix} );
49             }
50              
51 0         0 my $self = $class->SUPER::connect_cached(@args);
52              
53 0 0       0 $self->{private_dbix_placeholder_named_info}{prefix} =
54             defined $prefix ? $prefix : ':';
55 0 0       0 $self->{private_dbix_placeholder_named_info}{suffix} =
56             defined $suffix ? $suffix : '';
57              
58 0         0 return $self;
59             }
60              
61             package DBIx::Placeholder::Named::db;
62              
63 7     7   7805 use SQL::Tokenizer;
  7         12091  
  7         452  
64 7     7   59 use base qw(DBI::db);
  7         19  
  7         6113  
65              
66             sub prepare {
67 48     48   98665 my ( $dbh, $query ) = @_;
68              
69             # each token is analyzed. if the token starts with ':', it is pushed to
70             # @tao_dbi_placeholders. each element represents the named placeholder,
71             # and its index represents the order we will create the
72             # DBI::st::execute()'s argument (see Tao::DBI::st::execute()).
73             #
74             # TODO: someday we can benchmark this piece of code and check if using
75             # substr is more efficient.
76              
77 48         90 my @placeholders;
78 48         305 my @query_tokens = SQL::Tokenizer->tokenize($query);
79              
80 48         3141 my $prefix = $DBIx::Placeholder::Named::PREFIX;
81 48         1141 my $suffix = $DBIx::Placeholder::Named::SUFFIX;
82              
83 48 100       551 if ( exists $dbh->{private_dbix_placeholder_named_info} ) {
84 32         524 $prefix = $dbh->{private_dbix_placeholder_named_info}{prefix};
85 32         206 $suffix = $dbh->{private_dbix_placeholder_named_info}{suffix};
86             }
87              
88 48         741 my $prefix_length = length($prefix);
89 48         81 my $suffix_length = length($suffix);
90              
91 48         125 foreach my $token (@query_tokens) {
92 852         1280 my $token_length = length($token);
93 852 100 100     3402 if ( substr( $token, 0, $prefix_length ) eq $prefix
94             and substr( $token, $token_length - $suffix_length, $suffix_length ) eq $suffix )
95             {
96 48         102 my $token_stripped = substr( $token, $prefix_length );
97 48         1131 $token_stripped =
98             substr( $token_stripped, 0, length($token_stripped) - $suffix_length );
99 48         90 push @placeholders, $token_stripped;
100 48         280 $token = '?';
101             }
102             }
103              
104 48         446 my $new_query = join '', @query_tokens;
105              
106             # it's time to call DBI::st::prepare(). we use the modified tokenized
107             # query (with all named placeholders substituted by '?').
108              
109 48 100       558 my $sth = $dbh->SUPER::prepare($new_query)
110             or return;
111              
112             # we can now store the named placeholders array.
113 42         8684 $sth->{private_dbix_placeholder_named_info} =
114             { placeholders => \@placeholders };
115              
116 42         358 return $sth;
117             }
118              
119             package DBIx::Placeholder::Named::st;
120              
121 7     7   49 use base qw(DBI::st);
  7         16  
  7         4952  
122              
123             sub execute {
124 42     42   38017 my $sth = shift;
125              
126 42         76 my @params;
127              
128 42 100       183 if ( ref $_[0] eq 'HASH' ) {
129              
130             # create the DBI::st::execute()'s parameter. we iterate each named
131             # placeholder stored in Tao::DBI::db::prepare() and retrieve its value
132             # from the user supplied dictionary.
133              
134 48         201 @params =
135 30         141 map { $_[0]->{$_} } @{ $sth->{private_dbix_placeholder_named_info}->{placeholders} };
  30         353  
136              
137             }
138             else {
139              
140             # user haven't supplied a dictionary, so we use the parameters 'as is'
141 12         33 @params = @_;
142             }
143              
144             # DBI::st::execute() always returns.
145 42         2991835 my $rv = $sth->SUPER::execute(@params);
146              
147 42         1426 return $rv;
148             }
149              
150             1;
151              
152             =pod
153              
154             =head1 NAME
155              
156             DBIx::Placeholder::Named - DBI with named placeholders
157              
158             =head1 SYNOPSIS
159              
160             use DBIx::Placeholder::Named;
161              
162             my $dbh = DBIx::Placeholder::Named->connect($dsn, $user, $password)
163             or die DBIx::Placeholder::Named->errstr;
164              
165             my $sth = $dbh->prepare(
166             q{ INSERT INTO some_table (this, that) VALUES (:this, :that) }
167             )
168             or die $dbh->errstr;
169              
170             $sth->execute({ this => $this, that => $that, });
171              
172             $dbh =
173             DBIx::Placeholder::Named->connect( $dsn, $user, $password,
174             { PlaceholderPrefix => '__', PlaceholderSuffix => '**' } );
175              
176             my $sth = $dbh->prepare(
177             q{ INSERT INTO some_table (this, that) VALUES (__this**, __that**) }
178             );
179              
180             =head1 DESCRIPTION
181              
182             DBIx::Placeholder::Named is a subclass of DBI, which implements the ability
183             to understand named placeholders.
184              
185             =head1 METHODS
186              
187             =over 4
188              
189             =item DBIx::Placeholder::Named::connect()
190              
191             This method, overloaded from L, is responsible to create a
192             new connection to database. It is overloaded to accept new keywords
193             within the C<$attr> hash.
194              
195             my $dbh =
196             DBIx::Placeholder::Named->connect( $dsn, $user, $password,
197             { RaiseError => 1, PlaceholderSuffix => '', PlaceholderPrefix => ':', } );
198              
199             By default, C is C<:> and C is
200             empty string.
201              
202             =item DBIx::Placeholder::Named::connect_cached()
203              
204             This method, overloaded from L, is responsible to create a
205             cached connection to database. It is overloaded to accept new keywords
206             within the C<$attr> hash.
207              
208             my $dbh =
209             DBIx::Placeholder::Named->connect_cached( $dsn, $user, $password,
210             { RaiseError => 1, PlaceholderSuffix => '', PlaceholderPrefix => ':', } );
211              
212             By default, C is C<:> and C is
213             empty string.
214              
215             =item DBIx::Placeholder::Named::db::prepare()
216              
217             This method, overloaded from L, is responsible to create a prepared
218             statement for further execution. It is overloaded to accept a SQL query which
219             has named placeholders, like:
220              
221             SELECT a, b, c FROM t WHERE id = :id
222              
223             It uses L to correctly tokenize the SQL query,
224             preventing extract erroneous placeholders (date/time specifications, comments,
225             inside quotes or double quotes, etc).
226              
227             =item DBIx::Placeholder::Named::st::execute()
228              
229             =back
230              
231             =cut
232              
233             =head1 THANKS
234              
235             Gabor Szabo for requesting prefix support.
236              
237             =head1 AUTHOR
238              
239             Copyright (c) 2007, Igor Sutton Lopes "". All rights reserved.
240              
241             This module is free software; you can redistribute it and/or modify it under
242             the same terms as Perl itself.
243              
244             =head1 SEE ALSO
245              
246             L
247              
248             =cut
249