File Coverage

blib/lib/DBIx/AbstractStatement.pm
Criterion Covered Total %
statement 75 117 64.1
branch 6 14 42.8
condition 3 7 42.8
subroutine 16 25 64.0
pod 13 16 81.2
total 113 179 63.1


line stmt bran cond sub pod time code
1             package DBIx::AbstractStatement;
2 1     1   31563 use strict;
  1         2  
  1         408  
3              
4             =head1 NAME
5              
6             DBIx::AbstractStatement - SQL command kept together with the bindings
7              
8             =head1 SYNOPSIS
9              
10             use DBIx::AbstractStatement qw(sql sql_join);
11              
12             my $statement = sql('
13             SELECT * FROM customer c WHERE c.deleted is null');
14             # ordinary binding
15             if ($customer_id){
16             $statement->append(' AND c.id = :customer_id')
17             ->bind_param(':customer_id', $customer_id);
18             }
19              
20             # binding with sql
21             $statement->append(' AND :dt_created > :created')
22             $statement->bind_param(':created', $created || sql('sysdate'));
23            
24             # execute
25             $statement->dbh($dbh)->execute;
26             while(my @ary = $statement->sth->fetchrow_array){
27             }
28             ...
29              
30             # join
31             my $where = sql_join(
32             ($customer_name
33             ? sql('customer_name = :value')->bind_param(':value', $customer_name)
34             : ()),
35             ($from
36             ? sql('created >= :value')->bind_param(':value', $from)
37             : ()),
38             map {
39             sql("$_ = :$_")->bind_param(":$_", $args{$_})
40             } keys %args);
41              
42             =head1 DESCRIPTION
43              
44             The purpose of DBIx::AbstractStatement is to keep together
45             the SQL command and host variables bindings so
46             you can compose your SQL and bind host variables
47             simultaneously before DBH->prepare is called.
48              
49             A database handle to a statement can be supplied anytime before execute
50             is called or never if the particular statement is not about to be executed
51             but just used as a part of another statement.
52              
53             When execute is called on DBIx::AbstractStatement object,
54             the statement handle is prepared, all stored bindings performed on it,
55             and execute is called.
56              
57             =head2 FUNCTIONS IMPORTED ON DEMAND
58              
59             =over 4
60              
61             =cut
62              
63             our @EXPORT_OK = qw(sql sql_param sql_param_inout is_sql sql_join);
64             our @ISA = qw(Exporter Class::Accessor);
65             our $VERSION=0.09;
66             require Exporter;
67              
68 1     1   1233 use Class::Accessor;
  1         2644  
  1         7  
69             __PACKAGE__->mk_accessors(qw(text bindings numbered_params dbh));
70              
71 1     1   52 use constant 'BOUND_PARAM_SUFFIX' => '_dxas';
  1         8  
  1         1980  
72              
73             # setter returns the object
74 8     8 1 43 sub set { my $this = shift; $this->SUPER::set(@_); $this }
  8         31  
  8         63  
75              
76             # Exported methods - just shortcuts
77 9     9 1 625 sub sql { __PACKAGE__->new(@_) }
78 0     0 0 0 sub sql_param { __PACKAGE__->new(':v')->bind_param(':v', @_) }
79 0     0 0 0 sub sql_param_inout { __PACKAGE__->new(':v')->bind_param_inout(':v', @_) }
80 6     6 0 32 sub is_sql { UNIVERSAL::isa(shift(), __PACKAGE__) }
81             sub sql_join {
82 2     2 1 26 my($sep, @sql) = @_;
83 6         106 __PACKAGE__->new(
84             join($sep, map $_->text, @sql),
85 2         12 'bindings' => [ map @{$_->bindings}, @sql ]);
86             }
87              
88             =item sql($TEXT, %PARAMS)
89              
90             my $statement = DBIx::AbstractStatement->new("SELECT * FROM customer");
91             # or with imported sql
92             my $statement = sql("SELECT * FROM customer", 'numbered_params' => 1);
93            
94             A constructor (shortcut of Akar::DBI::Staement->new).
95             The $TEXT parameter is by no means required to be
96             a valid SQL statement.
97              
98             The parameters can be 'dbh' or 'numbered_params' described
99             as setter-getters further.
100              
101             =cut
102              
103             sub new {
104 11     11 1 34 my $proto = shift;
105 11         23 my($text, %params) = @_;
106              
107 11 50       31 $params{'text'} = defined($text)? $text: '';
108 11   100     49 $params{'bindings'} ||= [];
109 11         47 $proto->SUPER::new(\%params);
110             }
111              
112             =item sql_join($SEPARATOR, $SQL1, $SQL2, ...)
113            
114             my $sql = sql("SELECT * FROM customer WHERE ")->append(
115             sql_join(" AND ", map {
116             sql("$_ => :$_")->bind_param(":$_", $params{$_})
117             } keys(%params)));
118              
119             Returns a new sql. Joins both the texts and the bindings.
120              
121             =back
122              
123             =head1 METHODS
124              
125             =over 4
126              
127             =item bind_param($NAME, $VALUE)
128              
129             =item bind_param_inout($NAME, $VALUEREF, $SIZE)
130              
131             $statement->bind_param(':customer_id', $this->customer_id);
132              
133             # Oracle piece of PL/SQL decomposing an object into individual items
134             # Can be inserted into more complicated SQL statements
135             my $statement = sql("
136             :customer_id := l_payload.customer_id;
137             :action := l_payload.action;\n)
138             ->bind_param_inout(':customer_id', \$$this{'customer_id'}, 12)
139             ->bind_param_inout(':action', \$$this{'action'}, 128)
140              
141             # binding with statement
142             my $sql = sql("SELECT * FROM customer WHERE inserted > :inserted");
143             $sql->bind_param(':inserted', sql('sysdate'));
144              
145             # or even
146             $sql->bind_param(':inserted',
147             sql('sysdate - :days')->bind_param('days', $days));
148              
149             Stores an input or output binding for later usage.
150             Both methods accept the same parameters as their C<$sth-Ebind_param>,
151             C<$sth-Ebind_param_inout> DBI counterparts.
152             Both methods return the invocant.
153              
154             The name has to be :IDENTIFIER not :NUMBER.
155              
156             Value to bind can be DBIx::AbstractStatement object.
157             In this case every occurence of this parameter is
158             replaced by the text of the value.
159              
160             When parameter is bound an unique suffix is prepended to its name
161             to prevent name clash.
162              
163             =cut
164              
165 6     6 1 550 sub bind_param { shift(@_)->_bind_param('bind_param', @_) }
166              
167 0     0 1 0 sub bind_param_inout { shift(@_)->_bind_param('bind_param_inout', @_) }
168              
169             sub _param_re {
170 8     8   29 my $param_name = shift;
171 8         142 qr(\Q$param_name\E\b);
172             }
173              
174             # the bind variables have unique names
175             my $Cnt = 0;
176              
177             sub _bind_param {
178 6     6   10 my $this = shift;
179 6         16 my($method, $param_name, $value, @rest) = @_;
180              
181 6         6 my(@bindings, $replacement);
182 6 100       13 if (is_sql($value)){
183             # value is statement (is replaced in text)
184 3         11 $replacement = $value->text;
185 3         28 @bindings = @{$value->bindings};
  3         9  
186             }
187             else {
188             # value is value to bind
189 3         9 $replacement = $param_name. BOUND_PARAM_SUFFIX. ++$Cnt;
190 3         28 @bindings = DBIx::AbstractStatement::Binding->new({
191             'method' => $method,
192             'param_name' => $replacement,
193             'rest' => [$value, @rest]});
194             }
195 6         80 my $re = _param_re($param_name);
196 6 50       22 my $text = $this->text; $text =~ s/$re/$replacement/sg
  6         110  
197             or die sprintf "No occurence of %s in SQL string\n%s\n ", $param_name, $text;
198 6         82 $this->text($text);
199 6         9 push @{$this->bindings}, @bindings;
  6         14  
200 6         69 $this;
201             }
202              
203             =item has_param($NAME)
204              
205             $sql->bind_param(':created', sql('sysdate')) if $sql->has_param(':created');
206              
207             Returns true if statement contains the parameter.
208              
209             =cut
210              
211             # checks for param with certain name
212             sub has_param {
213 2     2 1 579 my $this = shift;
214 2         3 my($param_name) = @_;
215              
216 2         10 $this->text =~ _param_re($param_name);
217             }
218              
219             =item get_param_name($NAME)
220              
221             my $suffixed = $sql->get_param_name('customer_id');
222              
223             Simillar to has_param, but returns the name of the parameter -
224             suffixed if the parameter has already been bound.
225              
226             =cut
227              
228             # returns the new name of bind parameter
229             sub get_param_name {
230 1     1 1 6 my $this = shift;
231 1         3 my($param_name) = @_;
232              
233             # looking for
234 1         6 my $re = "\Q$param_name". "(?:". BOUND_PARAM_SUFFIX . "\\d+|\\b)";
235 1         41 my %names = map {
236 1         6 my($order) = /(\d+)$/;
237 1   50     8 $_ => $order || 0;
238             } $this->text =~ /($re)/sg;
239             # names are ordered by the parameter suffix
240 1         711 my @names = sort { $names{$a} <=> $names{$b} } keys %names;
  0         0  
241 1 50       9 wantarray? @names: $names[-1];
242             }
243              
244             =item dbh
245              
246             $statement->dbh($dbh); # setter
247             my $dbh = $statement->dbh; # getter
248              
249             Setter/getter for a database handle.
250              
251             =item sth
252              
253             my @ary = $this->sth->fetchrow_array
254              
255             Returns prepared (or prepared and executed) statement handle.
256             Calls dbh->prepare when called for the first time.
257              
258             =cut
259              
260             sub sth {
261 0     0 1 0 my $this = shift;
262 0   0     0 $$this{'_sth'} ||= do {
263 0 0       0 $this->_renumber_params if $this->numbered_params;
264 0         0 $this->dbh->prepare($this->text);
265             };
266             }
267              
268             =item execute
269              
270             $statement->execute
271              
272             Prepares statement handle, performs all bindings and calls execute on the handle.
273              
274             =cut
275              
276             sub execute {
277 0     0 1 0 my $this = shift;
278              
279 0         0 my $sth = $this->sth;
280             # process bindings
281 0         0 for my $binding (@{$this->bindings}){
  0         0  
282 0         0 my $method = $binding->method;
283 0         0 $sth->$method($binding->param_name, @{$binding->rest});
  0         0  
284             }
285 0         0 $sth->execute;
286             }
287              
288             =item numbered_params
289              
290             $sql->numbered_params(1);
291              
292             Setter-getter. If set to true, parameters in text and bindings
293             are modified from :IDENTIFIER style to C and :NUMBER style
294             right before the statement is prepared.
295              
296             =cut
297              
298             =item append
299              
300             $statement->append($text, $text2, ...);
301             $statement->append($statement, $statement, ...);
302              
303             Joins the statement. Accepts a list of statements or strings
304             (which are turned into statements).
305             The SQLs and bindings of these statements are appended to the invocant's
306             SQL and bindings. Returns the modified invocant.
307              
308             =cut
309              
310             sub append {
311 0     0 1 0 my $this = shift;
312              
313 0         0 my @list = _statement_list(@_);
314 0         0 $this->text( join('', map {$_->text} $this, @list));
  0         0  
315 0         0 push @{$this->bindings}, @{$_->bindings} for @list;
  0         0  
  0         0  
316 0         0 $this;
317             }
318              
319             =item prepend
320              
321             $statement->prepend($text, $text2, ...);
322             $statement->prepend($statement, $statement, ...);
323              
324             Simillar to append. The SQLs of statements are
325             joined together and prepended before the invocant's SQL.
326             Returns the modified invocant.
327              
328             =cut
329              
330             sub prepend {
331 0     0 1 0 my $this = shift;
332              
333 0         0 my @list = _statement_list(@_);
334 0         0 $this->text(join('', map {$_->text} @list, $this));
  0         0  
335 0         0 push @{$this->bindings}, @{$_->bindings} for @list;
  0         0  
  0         0  
336 0         0 $this;
337             }
338              
339             =item sprintf
340              
341             $statement->sprintf($text, $text2, ...);
342             $statement->sprintf($statement, $statement, ...);
343              
344             Simillar to append and prepend. The bindings of statements are
345             appended to the bindings of the invocant, while the invocant's
346             new SQL code is composed using sprintf with old SQL being the format.
347             Returns the modified invocant.
348              
349             =cut
350              
351             sub sprintf {
352 0     0 1 0 my $this = shift;
353            
354 0         0 my @list = _statement_list(@_);
355 0         0 $this->text(sprintf($this->text, map {$_->text} @list));
  0         0  
356 0         0 push @{$this->bindings}, @{$_->bindings} for @list;
  0         0  
  0         0  
357 0         0 $this;
358             }
359              
360             # makes list of statements from the mixed list of statements and strings
361             sub _statement_list {
362 0 0   0   0 map {ref($_)? $_: __PACKAGE__->new($_)} @_;
  0         0  
363             }
364              
365             # params changes from :statement_id, :type_id to 1, 2
366             sub _renumber_params {
367 1     1   6 my $this = shift;
368              
369 1 50       1 return unless @{$this->bindings};
  1         3  
370              
371 1         27 my %bindings = map {$_->param_name => $_} @{$this->bindings};
  2         29  
  1         4  
372 1         12 my @new_bindings;
373             my $replace_binding = sub {
374 2     2   5 my($param_name) = @_;
375 2         4 my $binding = $bindings{$param_name};
376              
377 2         8 push @new_bindings, $binding->new({
378             'param_name' => scalar(@new_bindings + 1),
379             'method' => $binding->method,
380             'rest' => $binding->rest});
381 2         58 '?';
382 1         5 };
383              
384 1         4 my $text = $this->text;
385             # reverse - longer names first
386 1         9 my $re = '('. join('|', map {$_->param_name} reverse @{$this->bindings}) . ')';
  2         18  
  1         3  
387 1         50 $text =~ s/$re/&$replace_binding($1)/sge;
  2         5  
388              
389 1         5 $this->text($text);
390 1         4 $this->bindings(\@new_bindings);
391             }
392              
393             {
394             package DBIx::AbstractStatement::Binding;
395 1     1   10 use base qw(Class::Accessor);
  1         2  
  1         166  
396             __PACKAGE__->mk_accessors(qw(method param_name rest));
397             }
398              
399              
400             =back
401              
402             =cut
403              
404             =head1 AUTHOR
405              
406             Roman Daniel
407              
408             =cut
409              
410             1;
411