File Coverage

blib/lib/DBIx/RoboQuery.pm
Criterion Covered Total %
statement 116 117 99.1
branch 51 56 91.0
condition 12 15 80.0
subroutine 28 28 100.0
pod 17 17 100.0
total 224 233 96.1


line stmt bran cond sub pod time code
1             # vim: set ts=2 sts=2 sw=2 expandtab smarttab:
2             #
3             # This file is part of DBIx-RoboQuery
4             #
5             # This software is copyright (c) 2010 by Randy Stauner.
6             #
7             # This is free software; you can redistribute it and/or modify it under
8             # the same terms as the Perl 5 programming language system itself.
9             #
10 8     8   92929 use strict;
  8         17  
  8         356  
11 8     8   46 use warnings;
  8         13  
  8         647  
12              
13             package DBIx::RoboQuery;
14             {
15             $DBIx::RoboQuery::VERSION = '0.032';
16             }
17             # git description: v0.031-6-gaa2e204
18              
19             BEGIN {
20 8     8   175 $DBIx::RoboQuery::AUTHORITY = 'cpan:RWSTAUNER';
21             }
22             # ABSTRACT: Very configurable/programmable query object
23              
24 8     8   61 use Carp qw(carp croak);
  8         15  
  8         626  
25 8     8   5764 use DBIx::RoboQuery::ResultSet ();
  8         53  
  8         185  
26 8     8   5831 use DBIx::RoboQuery::Util ();
  8         20  
  8         178  
27 8     8   11229 use Template 2.22; # Template Toolkit
  8         544662  
  8         14115  
28              
29             # no warnings 'once';
30             my $_template_stash_private = $Template::Stash::PRIVATE;
31              
32              
33             sub new {
34 69     69 1 39355 my $class = shift;
35 69 100       351 my %opts = @_ == 1 ? %{ $_[0] } : @_;
  17         75  
36              
37             # Params::Validate not currently warranted
38             # (since it's still missing the "mutually exclusive" feature)
39              
40             # defaults
41 69         542 my $self = {
42             drop_columns => [],
43             key_columns => [],
44             resultset_class => "${class}::ResultSet",
45             variables => {},
46             template_tr_name => 'template',
47             };
48              
49 69         213 bless $self, $class;
50              
51 69         240 foreach my $var ( $self->_pass_through_args() ){
52 1035 100       2193 $self->{$var} = $opts{$var} if exists($opts{$var});
53             }
54              
55 69         332 DBIx::RoboQuery::Util::_ensure_arrayrefs($self);
56              
57 69 100 100     491 croak(q|Cannot include both 'sql' and 'file'|)
58             if exists($opts{sql}) && exists($opts{file});
59              
60             # if the string is defined that's good enough
61 68 100       175 if( defined($opts{sql}) ){
    100          
62 66 50       242 $self->{template} = ref($opts{sql}) ? ${$opts{sql}} : $opts{sql};
  0         0  
63             }
64             # the file path should at least be a true value
65             elsif( my $f = $opts{file} ){
66 1 50       64 open(my $fh, '<', $f)
67             or croak("Failed to open '$f': $!");
68 1         2 $self->{template} = do { local $/; <$fh>; };
  1         5  
  1         49  
69             }
70             else {
71 1         14 croak(q|Must specify one of 'sql' or 'file'|);
72             }
73              
74 67         197 $self->prepare_transformations();
75              
76 67         246 $self->{tt} ||= Template->new({
77             ABSOLUTE => 1,
78             STRICT => 1,
79             VARIABLES => {
80             query => $self,
81 67 100       748 %{$self->{variables}}
82             },
83 67 50 33     320 %{ $self->{template_options} || {} },
84             })
85             or die "$class error: Template::Toolkit failed: $Template::ERROR\n";
86              
87 67         711191 return $self;
88             }
89              
90             # convenience method for subclasses
91              
92             sub _arrayref_args {
93 206     206   958 qw(
94             bind_params
95             drop_columns
96             key_columns
97             order
98             );
99             }
100              
101              
102             sub bind {
103 13     13 1 6373 my ($self, @bind) = @_;
104              
105 13   100     75 my $bound = $self->{bind_params} ||= [];
106              
107             # auto-increment placeholder index unless all three values were passed
108 13 100       46 unshift @bind, ++$self->{bind_params_index}
109             unless @bind == 3;
110              
111             # always push (don't set $bound->[$index]) because we're just going
112             # to pass all of these to bind_param() in order
113 13         23 push @$bound, \@bind;
114              
115             # convenience for putting directly into place in sql
116 13 100       94 return $bind[0] =~ /^\d+$/ ? '?' : $bind[0];
117             }
118              
119              
120             sub bound_params {
121 22 100   22 1 1266 return @{ $_[0]->{bind_params} || [] };
  22         178  
122             }
123              
124              
125             sub bound_values {
126 11     11 1 32 return map { $_->[1] } $_[0]->bound_params;
  31         94  
127             }
128              
129              
130             sub drop_columns {
131 4     4 1 15 my ($self) = shift;
132 4 100       15 $self->{drop_columns} = [DBIx::RoboQuery::Util::_flatten(@_)]
133             if @_;
134 4         5 return @{$self->{drop_columns}};
  4         22  
135             }
136              
137              
138             sub key_columns {
139 19     19 1 5147 my ($self) = shift;
140 19 100       75 $self->{key_columns} = [DBIx::RoboQuery::Util::_flatten(@_)]
141             if @_;
142 19         31 return @{$self->{key_columns}};
  19         97  
143             }
144              
145              
146             sub order {
147 10     10 1 926 my ($self) = shift;
148 10 100       38 if( @_ ){
    100          
149 2         9 $self->{order} = [DBIx::RoboQuery::Util::_flatten(@_)]
150             }
151             # only if not previously set (empty arrayref counts as being set)
152             elsif( !$self->{order} ){
153 2         13 $self->{order} = [
154             DBIx::RoboQuery::Util::order_from_sql(
155             $self->sql, $self)
156             ]
157             }
158 10         15 return @{$self->{order}};
  10         51  
159             }
160              
161             # convenience method: args allowed in the constructor
162              
163             sub _pass_through_args {
164             (
165 69     69   240 $_[0]->_arrayref_args,
166             qw(
167             dbh
168             default_slice
169             prefix
170             resultset_class
171             squeeze_blank_lines
172             suffix
173             template_options
174             transformations
175             template_private_vars
176             template_tr_name
177             variables
178             ));
179             }
180              
181              
182             sub prepare_transformations {
183 67     67 1 188 my ($self) = @_;
184              
185             return
186 67 100       233 unless my $tr = $self->{transformations};
187              
188             # assume a simple hash is a hash of named subs
189 12 50       41 if( ref $tr eq 'HASH' ){
190 12         2826 require Sub::Chain::Group;
191              
192             # the name of the template func can be changed (or disabled)
193 12 100       101666 if( my $tr_name = $self->{template_tr_name} ){
194             # add the template callback to the subs (with $self embedded)
195 11 100       86 $tr = {
196             %$tr,
197             $tr_name => $self->template_tr_callback,
198             }
199             if ! exists $tr->{ $tr_name };
200             }
201              
202 12         102 $self->{transformations} =
203             Sub::Chain::Group->new(
204             chain_class => 'Sub::Chain::Named',
205             chain_args => {subs => $tr},
206             hook_as_hash => 1,
207             );
208             }
209              
210             # return nothing
211 12         5421 return;
212             }
213              
214              
215             sub pre_process_sql {
216 49     49 1 82 my ($self, $sql) = @_;
217 49 100       144 $sql = $self->{prefix} . $sql if defined $self->{prefix};
218 49 100       124 $sql = $sql . $self->{suffix} if defined $self->{suffix};
219 49         97 return $sql;
220             }
221              
222              
223             sub prefer {
224 19     19 1 4565 my ($self) = shift;
225 19   100     32 push(@{ $self->{preferences} ||= [] }, @_);
  19         149  
226             }
227              
228             sub _process_template {
229 126     126   208 my ($self, $template, $vars) = @_;
230 126         168 my $output = '';
231              
232             # this is a regexp for vars that are considered private (will appear undef in template)
233 126 100       328 local $Template::Stash::PRIVATE = $self->{template_private_vars}
234             if exists $self->{template_private_vars};
235              
236 126 100       497 $self->{tt}->process($template, $vars, \$output)
237             or die($self->{tt}->error(), "\n");
238              
239 124         690650 return $output;
240             }
241              
242              
243             sub resultset {
244 23     23 1 1391 my ($self) = shift;
245             # cache this object to avoid confusion
246 23   66     92 return $self->{resultset} ||= do {
247             # taint check
248 23         87 (my $class = $self->{resultset_class}) =~ s/[^a-zA-Z0-9_:']+//g;
249             # make sure it's loaded first
250 23         1525 eval "require $class";
251 23 100       301 die $@ if $@;
252              
253 22         133 $class->new($self);
254             }
255             }
256              
257              
258             sub sql {
259 67     67 1 402 my ($self, $vars) = @_;
260 67         83 my $output;
261              
262             # Cache the result to avoid duplicating function calls,
263             # directives, template logic, etc.
264             # Plus it shouldn't need to be run more than once.
265 67 100       184 if( exists $self->{processed_sql} ){
266 18         43 $output = $self->{processed_sql};
267             }
268             else {
269 49   100     199 $vars ||= {};
270 49         155 my $sql = $self->pre_process_sql($self->{template});
271 49         139 $output = $self->_process_template(\$sql, $vars);
272              
273             # this is fairly naive, but for SQL would usually be fine
274 49 100       194 $output =~ s/\n\s*\n+/\n/g
275             if $self->{squeeze_blank_lines};
276              
277 49         151 $self->{processed_sql} = $output;
278             }
279 67         392 return $output;
280             }
281              
282              
283             sub transform {
284 17     17 1 10845 my ($self, @tr) = @_;
285              
286 17 50       76 croak("Cannot transform without 'transformations'")
287             unless my $tr = $self->{transformations};
288              
289 17         68 $tr->append(@tr);
290             }
291              
292             # shortcuts
293              
294              
295             sub tr_fields {
296 4     4 1 5478 my ($self, $name, $fields, @args) = @_;
297 4         21 return $self->transform($name, fields => $fields, args => [@args]);
298             }
299              
300             sub tr_groups {
301 2     2 1 1862 my ($self, $name, $groups, @args) = @_;
302 2         12 return $self->transform($name, groups => $groups, args => [@args]);
303             }
304              
305              
306             sub tr_row {
307 5     5 1 1647 my ($self, $name, $hooks, @args) = @_;
308 5         48 return $self->transform($name, hooks => $hooks, args => [@args]);
309             }
310              
311              
312             sub template_tr_callback {
313 11     11 1 954 my ($self) = @_;
314             return sub {
315 5     5   4171 my ($row, $template) = @_;
316             # (updating the hash should work, but if not this would: '[% _save_row(row) %]')
317 5         15 $template = '[% ' . $template . ' %]';
318 5         28 $self->_process_template(\$template, {row => $row});
319 5         27 return $row;
320 11         91 };
321             }
322              
323             1;
324              
325             __END__