File Coverage

blib/lib/Class/DBI/Plugin/DeepAbstractSearchPager.pm
Criterion Covered Total %
statement 27 98 27.5
branch 2 50 4.0
condition 0 35 0.0
subroutine 7 13 53.8
pod 3 4 75.0
total 39 200 19.5


line stmt bran cond sub pod time code
1             package Class::DBI::Plugin::DeepAbstractSearchPager;
2 1     1   167686 use strict;
  1         6  
  1         43  
3 1     1   7 use warnings;
  1         2  
  1         34  
4 1     1   6 use Carp;
  1         6  
  1         98  
5              
6 1     1   1017 use UNIVERSAL::require;
  1         2193  
  1         10  
7 1     1   34 use base qw( Data::Page Class::Data::Inheritable );
  1         2  
  1         1041  
8              
9             our $VERSION = 0.04;
10              
11             # D::P inherits from Class::Accessor::Chained::Fast
12             __PACKAGE__->mk_accessors( qw( where abstract_attr per_page page order_by _cdbi_app ) );
13              
14             __PACKAGE__->mk_classdata( '_syntax' );
15             __PACKAGE__->mk_classdata( '_deep_pager_class' );
16              
17              
18             =head1 NAME
19              
20             Class::DBI::Plugin::DeepAbstractSearchPager - paged queries for CDBI::Plugin::DeepAbstractSearch
21              
22             =head1 DESCRIPTION
23              
24             Adds a deep_pager method to your class that can query using SQL::Abstract where clauses
25             with joined table fields as described by C,
26             and limit the number of rows returned to a specific subset.
27              
28             =head1 SYNOPSIS
29              
30             package CD;
31             use base 'Class::DBI';
32              
33             use Class::DBI::Plugin::DeepAbstractSearch; # pager needs this
34             use Class::DBI::Plugin::DeepAbstractSearchPager;
35              
36             # or to use a different syntax
37             # use Class::DBI::Plugin::Pager::RowsTo;
38              
39             __PACKAGE__->set_db(...);
40              
41              
42             # in a nearby piece of code...
43              
44             use CD;
45              
46             # see SQL::Abstract for how to specify the query
47             my $where = { 'artist.name' => { -like => '%Elvis%' } };
48              
49             my $order_by => 'artist.name, title';
50              
51             # bit by bit:
52             my $pager = CD->deep_pager;
53              
54             $pager->per_page( 10 );
55             $pager->page( 3 );
56             $pager->where( $where );
57             $pager->order_by( $order_by );
58              
59             $pager->set_syntax( 'RowsTo' );
60              
61             my @cds = $pager->deep_search_where;
62              
63             # or all at once
64             my $pager = CD->deep_pager( $where, $order_by, 10, 3 );
65              
66             my @cds = $pager->deep_search_where;
67              
68             # or
69              
70             my $pager = CD->deep_pager;
71              
72             my @cds = $pager->deep_search_where( $where, $order_by, 10, 3 );
73              
74             # $pager isa Data::Page
75             # @cds contains the CDs just for the current page
76              
77             =head1 METHODS
78              
79             =over
80              
81             =item import
82              
83             Loads the C method into the CDBI app.
84              
85             =cut
86              
87             sub import {
88 1     1   9 my ( $class ) = @_; # the pager class or subclass
89              
90 1         5 __PACKAGE__->_deep_pager_class( $class );
91              
92 1         77 my $caller;
93              
94             # find the app
95 1         5 foreach my $level ( 0 .. 10 )
96             {
97 1         4 $caller = caller( $level );
98 1 50       10 last if UNIVERSAL::isa( $caller, 'Class::DBI' )
99             }
100              
101 1 50       5 croak( "can't find the CDBI app" ) unless $caller;
102              
103 1     1   3996 no strict 'refs';
  1         2  
  1         1364  
104 1         3 *{"$caller\::deep_pager"} = \&deep_pager;
  1         18  
105             }
106              
107              
108             =item pager( [$where, [$abstract_attr]], [$order_by], [$per_page], [$page], [$syntax] )
109              
110             Also accepts named arguments:
111              
112             where => $where,
113             abstract_attr => $attr,
114             order_by => $order_by,
115             per_page => $per_page,
116             page => $page,
117             syntax => $syntax
118              
119             Returns a pager object. This subclasses L.
120              
121             Note that for positional arguments, C<$abstract_attr> can only be passed if
122             preceded by a C<$where> argument.
123              
124             C<$abstract_attr> can contain the C<$order_by> setting (just as in
125             L).
126              
127             =over 4
128              
129             =item configuration
130              
131             The named arguments all exist as get/set methods.
132              
133             =over 4
134              
135             =item where
136              
137             A hashref specifying the query. See L.
138              
139             =item abstract_attr
140              
141             A hashref specifying extra options to be passed through to the
142             L constructor.
143              
144             =item order_by
145              
146             Single column name, string of column names or array ref of column names
147             for the ORDER BY clause. Defaults to the primary key(s) if not set.
148              
149             =item per_page
150              
151             Number of results per page.
152              
153             =item page
154              
155             The pager will retrieve results just for this page. Defaults to 1.
156              
157             =item syntax
158              
159             Change the way the 'limit' clause is constructed. See C. Default
160             is C.
161              
162             =back
163              
164             =back
165              
166             =cut
167              
168             sub deep_pager
169             {
170 0     0 0   my $cdbi = shift;
171            
172 0           my $class = __PACKAGE__->_deep_pager_class;
173              
174 0           my $self = bless {}, $class;
175              
176 0           $self->_cdbi_app( $cdbi );
177              
178             # This has to come before _init, so the caller can choose to set the syntax
179             # instead. But don't auto-set if we're a subclass.
180 0 0         $self->auto_set_syntax if $class eq __PACKAGE__;
181              
182 0           $self->_init( @_ );
183              
184 0           return $self;
185             }
186              
187             # _init is also called by results, so preserve any existing settings if
188             # new settings are not provided
189             sub _init {
190 0     0     my $self = shift;
191              
192 0 0         return unless @_;
193              
194 0           my ( $where, $abstract_attr, $order_by, $per_page, $page, $syntax );
195              
196 0 0 0       if ( ref( $_[0] ) or $_[0] =~ /^\d+$/ )
197             {
198 0 0         $where = shift if ref $_[0] eq 'HASH';
199 0 0         $abstract_attr = shift if ref $_[0] eq 'HASH';
200 0 0         $order_by = shift unless $_[0] =~ /^\d+$/;
201 0 0 0       $per_page = shift if $_[0] && $_[0] =~ /^\d+$/;
202 0 0 0       $page = shift if $_[0] && $_[0] =~ /^\d+$/;
203 0           $syntax = shift;
204             }
205             else
206             {
207 0           my %args = @_;
208              
209 0           $where = $args{where};
210 0           $abstract_attr = $args{abstract_attr};
211 0           $order_by = $args{order_by};
212 0           $per_page = $args{per_page};
213 0           $page = $args{page};
214 0           $syntax = $args{syntax};
215             }
216              
217 0 0 0       $order_by = delete $$abstract_attr{order_by} if ($abstract_attr and !$order_by);
218              
219 0 0         $self->per_page( $per_page ) if $per_page;
220 0 0         $self->set_syntax( $syntax ) if $syntax;
221 0 0         $self->abstract_attr( $abstract_attr )if $abstract_attr;
222 0 0         $self->where( $where ) if $where;
223 0 0         $self->order_by( $order_by ) if $order_by;
224 0 0         $self->page( $page ) if $page;
225             }
226              
227             =item deep_search_where
228              
229             Retrieves results from the pager. Accepts the same arguments as the C
230             method.
231              
232             =cut
233              
234             # like CDBI::Plugin::DeepAbstractSearch::deep_search_where, with extra limitations
235             sub deep_search_where {
236 0     0 1   my $self = shift;
237              
238 0           $self->_init( @_ );
239              
240 0           $self->_setup_pager;
241              
242 0           my $cdbi = $self->_cdbi_app;
243              
244 0   0       my ($what, $from, $where, $bind) = $cdbi->get_deep_where($self->where,
245             { order_by => $self->order_by || [ $cdbi->primary_columns ] } );
246              
247 0   0       my $syntax = $self->_syntax || $self->set_syntax;
248 0           my $limit_phrase = $self->$syntax;
249              
250 0           my $sql = <<"";
251             SELECT $what
252             FROM $from
253             WHERE $where
254             $limit_phrase
255              
256 0           return $cdbi->sth_to_objects($cdbi->sql_deeply_and_broadly($sql), $bind);
257             }
258              
259             sub _setup_pager {
260 0     0     my ( $self ) = @_;
261              
262 0   0       my $where = $self->where || croak( 'must set a query before retrieving results' );
263 0   0       my $per_page = $self->per_page || croak( 'no. of entries per page not specified' );
264 0           my $cdbi = $self->_cdbi_app;
265 0           my $count = $cdbi->count_deep_search_where( $where );
266 0   0       my $page = $self->page || 1;
267              
268 0           $self->total_entries( $count );
269 0           $self->entries_per_page( $per_page );
270 0           $self->current_page( $page );
271            
272 0 0         croak( 'Fewer than one entry per page!' ) if $self->entries_per_page < 1;
273              
274 0 0         $self->current_page( $self->first_page ) unless defined $self->current_page;
275 0 0         $self->current_page( $self->first_page ) if $self->current_page < $self->first_page;
276 0 0         $self->current_page( $self->last_page ) if $self->current_page > $self->last_page;
277             }
278              
279             =item set_syntax( [ $name || $class || $coderef ] )
280              
281             Changes the syntax used to generate the C or other phrase that restricts
282             the results set to the required page.
283              
284             The syntax is implemented as a method called on the pager, which can be
285             queried to provide the C<$rows> and C<$offset> parameters (see the subclasses
286             included in this distribution).
287              
288             =over 4
289              
290             =item $class
291              
292             A class with a C method.
293              
294             =item $name
295              
296             Name of a class in the C namespace, which has a
297             C method.
298              
299             =item $coderef
300              
301             Will be called as a method on the pager object, so receives the pager as its
302             argument.
303              
304             =item (no args)
305              
306             Called without args, will default to C, which causes
307             L
308             to be used.
309              
310             =back
311              
312             =cut
313              
314             sub set_syntax {
315 0     0 1   my ( $proto, $syntax ) = @_;
316              
317             # pick up default from subclass, or load from LimitOffset
318 0   0       $syntax ||= $proto->can( 'make_limit' );
319 0   0       $syntax ||= 'LimitOffset';
320              
321 0 0         if ( ref( $syntax ) eq 'CODE' )
322             {
323 0           $proto->_syntax( $syntax );
324 0           return $syntax;
325             }
326              
327 0 0         my $format_class = $syntax =~ '::' ? $syntax : "Class::DBI::Plugin::Pager::$syntax";
328              
329 0 0         $format_class->require || croak "error loading $format_class: $UNIVERSAL::require::ERROR";
330              
331 0   0       my $formatter = $format_class->can( 'make_limit' ) || croak "no make_limit method in $format_class";
332              
333 0           $proto->_syntax( $formatter );
334              
335 0           return $formatter;
336             }
337              
338             =item auto_set_syntax
339              
340             This is called automatically when you call C, and attempts to set the
341             syntax automatically.
342              
343             If you are using a subclass of the pager, this method will not be called.
344              
345             Will C if using Oracle or DB2, since there is no simple syntax for limiting
346             the results set. DB2 has a C keyword, but that seems to apply to a
347             cursor and I don't know if there is a cursor available to the pager. There
348             should probably be others to add to the unsupported list.
349              
350             Supports the following drivers:
351              
352             DRIVER CDBI::P::Pager subclass
353             my %supported = ( pg => 'LimitOffset',
354             mysql => 'LimitOffset', # older versions need LimitXY
355             sqlite => 'LimitOffset', # or LimitYX
356             interbase => 'RowsTo',
357             firebird => 'RowsTo',
358             );
359              
360             Older versions of MySQL should use the LimitXY syntax. You'll need to set it
361             manually, either by C, or by passing
362             C 'LimitXY'> to a method call, or call C directly.
363              
364             Any driver not in the supported or unsupported lists defaults to LimitOffset.
365              
366             Any additions to the supported and unsupported lists gratefully received.
367              
368             =cut
369              
370             sub auto_set_syntax {
371 0     0 1   my ( $self ) = @_;
372              
373             # not an exhaustive list
374 0           my %not_supported = ( oracle => 'Oracle',
375             db2 => 'DB2',
376             );
377              
378             # additions welcome
379 0           my %supported = ( pg => 'LimitOffset',
380             mysql => 'LimitOffset', # older versions need LimitXY
381             sqlite => 'LimitOffset', # or LimitYX
382             interbase => 'RowsTo',
383             firebird => 'RowsTo',
384             );
385              
386 0           my $cdbi = $self->_cdbi_app;
387              
388 0           my $driver = lc( $cdbi->__driver );
389              
390 0 0         die __PACKAGE__ . " can't build limit clauses for $not_supported{ $driver }"
391             if $not_supported{ $driver };
392              
393 0   0       $self->set_syntax( $supported{ $driver } || 'LimitOffset' );
394             }
395              
396             1;
397              
398             __END__