File Coverage

blib/lib/Class/DBI/Plugin/Pager.pm
Criterion Covered Total %
statement 120 122 98.3
branch 44 62 70.9
condition 23 43 53.4
subroutine 16 16 100.0
pod 5 5 100.0
total 208 248 83.8


line stmt bran cond sub pod time code
1             package Class::DBI::Plugin::Pager;
2 5     5   306700 use strict;
  5         12  
  5         159  
3 5     5   24 use warnings;
  5         9  
  5         112  
4 5     5   24 use Carp;
  5         14  
  5         428  
5            
6 5     5   5105 use UNIVERSAL::require;
  5         8991  
  5         50  
7 5     5   8028 use SQL::Abstract;
  5         60104  
  5         91  
8            
9 5     5   262 use base qw( Data::Page Class::Data::Inheritable );
  5         14  
  5         6360  
10            
11 5     5   36062 use vars qw( $VERSION );
  5         16  
  5         787  
12            
13             $VERSION = '0.566';
14            
15             # D::P inherits from Class::Accessor::Chained::Fast
16             __PACKAGE__->mk_accessors( qw( where abstract_attr per_page page order_by _cdbi_app ) );
17            
18             __PACKAGE__->mk_classdata( '_syntax' );
19             __PACKAGE__->mk_classdata( '_pager_class' );
20            
21            
22             =head1 NAME
23            
24             Class::DBI::Plugin::Pager - paged queries for CDBI
25            
26             =head1 DESCRIPTION
27            
28             Adds a pager method to your class that can query using SQL::Abstract where clauses,
29             and limit the number of rows returned to a specific subset.
30            
31             =head1 SYNOPSIS
32            
33             package CD;
34             use base 'Class::DBI';
35            
36             use Class::DBI::Plugin::AbstractCount; # pager needs this
37             use Class::DBI::Plugin::Pager;
38            
39             # or to use a different syntax
40             # use Class::DBI::Plugin::Pager::RowsTo;
41            
42             __PACKAGE__->set_db(...);
43            
44            
45             # in a nearby piece of code...
46            
47             use CD;
48            
49             # see SQL::Abstract for how to specify the query
50             my $where = { ... };
51            
52             my $order_by => [ qw( foo bar ) ];
53            
54             # bit by bit:
55             my $pager = CD->pager;
56            
57             $pager->per_page( 10 );
58             $pager->page( 3 );
59             $pager->where( $where );
60             $pager->order_by( $order_by );
61            
62             $pager->set_syntax( 'RowsTo' );
63            
64             my @cds = $pager->search_where;
65            
66             # or all at once
67             my $pager = CD->pager( $where, $order_by, 10, 3 );
68            
69             my @cds = $pager->search_where;
70            
71             # or
72            
73             my $pager = CD->pager;
74            
75             my @cds = $pager->search_where( $where, $order_by, 10, 3 );
76            
77             # $pager isa Data::Page
78             # @cds contains the CDs just for the current page
79            
80             =head1 METHODS
81            
82             =over
83            
84             =item import
85            
86             Loads the C method into the CDBI app.
87            
88             =cut
89            
90             sub import {
91 4     4   49 my ( $class ) = @_; # the pager class or subclass
92            
93 4         21 __PACKAGE__->_pager_class( $class );
94            
95 4         37 my $caller;
96            
97             # find the app - supports subclassing (My::Pager is_a CDBI::P::Pager, not_a CDBI)
98 4         15 foreach my $level ( 0 .. 10 )
99             {
100 4         21 $caller = caller( $level );
101 4 50       100 last if UNIVERSAL::isa( $caller, 'Class::DBI' )
102             }
103            
104 4 50       17 warn( "can't find the CDBI app" ), return unless $caller;
105             #croak( "can't find the CDBI app" ) unless $caller;
106            
107 5     5   31 no strict 'refs';
  5         10  
  5         7511  
108 4         10 *{"$caller\::pager"} = \&pager;
  4         8682  
109             }
110            
111             =item pager( [$where, [$abstract_attr]], [$order_by], [$per_page], [$page], [$syntax] )
112            
113             Also accepts named arguments:
114            
115             where => $where,
116             abstract_attr => $attr,
117             order_by => $order_by,
118             per_page => $per_page,
119             page => $page,
120             syntax => $syntax
121            
122             Returns a pager object. This subclasses L.
123            
124             Note that for positional arguments, C<$abstract_attr> can only be passed if
125             preceded by a C<$where> argument.
126            
127             C<$abstract_attr> can contain the C<$order_by> setting (just as in
128             L).
129            
130             =over 4
131            
132             =item configuration
133            
134             The named arguments all exist as get/set methods.
135            
136             =over 4
137            
138             =item where
139            
140             A hashref specifying the query. See L.
141            
142             =item abstract_attr
143            
144             A hashref specifying extra options to be passed through to the
145             L constructor.
146            
147             =item order_by
148            
149             Single column name or arrayref of column names for the ORDER BY clause.
150             Defaults to the primary key(s) if not set.
151            
152             =item per_page
153            
154             Number of results per page.
155            
156             =item page
157            
158             The pager will retrieve results just for this page. Defaults to 1.
159            
160             =item syntax
161            
162             Change the way the 'limit' clause is constructed. See C. Default
163             is C.
164            
165             =back
166            
167             =back
168            
169             =cut
170            
171             sub pager {
172 15     15 1 17461 my $cdbi = shift;
173            
174 15         101 my $class = __PACKAGE__->_pager_class;
175            
176 15         149 my $self = bless {}, $class;
177            
178 15         80 $self->_cdbi_app( $cdbi );
179            
180             # This has to come before _init, so the caller can choose to set the syntax
181             # instead. But don't auto-set if we're a subclass.
182 15 100       241 $self->auto_set_syntax if $class eq __PACKAGE__;
183            
184 15         62 $self->_init( @_ );
185            
186 15         200 return $self;
187             }
188            
189             # _init is also called by results, so preserve any existing settings if
190             # new settings are not provided
191             sub _init {
192 30     30   53 my $self = shift;
193            
194 30 100       86 return unless @_;
195            
196 12         18 my ( $where, $abstract_attr, $order_by, $per_page, $page, $syntax );
197            
198 12 100 66     70 if ( ref( $_[0] ) or $_[0] =~ /^\d+$/ )
199             {
200 7 50       25 $where = shift if ref $_[0]; # SQL::Abstract accepts a hashref or an arrayref
201 7 100       25 $abstract_attr = shift if ref $_[0] eq 'HASH';
202             # $order_by = shift unless $_[0] =~ /^\d+$/;
203             # $per_page = shift if $_[0] =~ /^\d+$/;
204             # $page = shift if $_[0] =~ /^\d+$/;
205 7 100 66     74 $order_by = shift unless $_[0] and $_[0] =~ /^\d+$/;
206 7 50 33     58 $per_page = shift if $_[0] and $_[0] =~ /^\d+$/;
207 7 50 33     63 $page = shift if $_[0] and $_[0] =~ /^\d+$/;
208 7         12 $syntax = shift;
209             }
210             else
211             {
212 5         22 my %args = @_;
213            
214 5         10 $where = $args{where};
215 5         9 $abstract_attr = $args{abstract_attr};
216 5         8 $order_by = $args{order_by};
217 5         7 $per_page = $args{per_page};
218 5         8 $page = $args{page};
219 5         15 $syntax = $args{syntax};
220             }
221            
222             # Emulate AbstractSearch's search_where ordering -VV 20041209
223 12 100 100     48 $order_by = delete $$abstract_attr{order_by} if ($abstract_attr and !$order_by);
224            
225 12 50       69 $self->per_page( $per_page ) if $per_page;
226 12 100       109 $self->set_syntax( $syntax ) if $syntax;
227 12 100       45 $self->abstract_attr( $abstract_attr )if $abstract_attr;
228 12 50       76 $self->where( $where ) if $where;
229 12 50       140 $self->order_by( $order_by ) if $order_by;
230 12 50       115 $self->page( $page ) if $page;
231             }
232            
233             =item search_where
234            
235             Retrieves results from the pager. Accepts the same arguments as the C
236             method.
237            
238             =cut
239            
240             # like CDBI::AbstractSearch::search_where, with extra limitations
241             sub search_where {
242 15     15 1 9809 my $self = shift;
243            
244 15         90 $self->_init( @_ );
245            
246 15         79 $self->_setup_pager;
247            
248 15         1011 my $cdbi = $self->_cdbi_app;
249            
250 15   50     114 my $order_by = $self->order_by || [ $cdbi->primary_columns ];
251 15         176 my $where = $self->where;
252 15   66     118 my $syntax = $self->_syntax || $self->set_syntax;
253 15         147 my $limit_phrase = $self->$syntax;
254 15 100       28 my $sql = SQL::Abstract->new( %{ $self->abstract_attr || {} } );
  15         49  
255            
256 15 100       1072 $order_by = [ $order_by ] unless ref $order_by;
257 15         66 my ( $phrase, @bind ) = $sql->where( $where, $order_by );
258            
259             # If the phrase starts with the ORDER clause (i.e. no WHERE spec), then we are
260             # emulating a { 1 => 1 } search, but avoiding the bug in Class::DBI::Plugin::AbstractCount 0.04,
261             # so we need to replace the spec - patch from Will Hawes
262 15 100       5824 if ( $phrase =~ /^\s*ORDER\s*/i )
263             {
264 2         5 $phrase = ' 1=1' . $phrase;
265             }
266            
267            
268 15         34 $phrase .= ' ' . $limit_phrase;
269 15         65 $phrase =~ s/^\s*WHERE\s*//i;
270            
271 15         61 return $cdbi->retrieve_from_sql( $phrase, @bind );
272             }
273            
274             =item retrieve_all
275            
276             Convenience method, generates a WHERE clause that matches all rows from the table.
277            
278             Accepts the same arguments as the C or C methods, except that no
279             WHERE clause should be specified.
280            
281             Note that the argument parsing routine called by the C method cannot cope with
282             positional arguments that lack a WHERE clause, so either use named arguments, or the
283             'bit by bit' approach, or pass the arguments directly to C.
284            
285             =cut
286            
287             sub retrieve_all
288             {
289 2     2 1 441 my $self = shift;
290            
291 2         5 my $get_all = {}; # { 1 => 1 };
292            
293 2 100       8 unless ( @_ )
294             { # already set pager up via method calls
295 1         4 $self->where( $get_all );
296 1         8 return $self->search_where;
297             }
298            
299 1 50 33     10 my @args = ( ref( $_[0] ) or $_[0] =~ /^\d+$/ ) ?
300             ( $get_all, @_ ) : # send an array
301             ( where => $get_all, @_ ); # send a hash
302            
303 1         4 return $self->search_where( @args );
304             }
305            
306             sub _setup_pager
307             {
308 15     15   24 my ( $self ) = @_;
309            
310 15   50     51 my $where = $self->where || {};
311            
312             # fix { 1 => 1 } as a special case - Class::DBI::Plugin::AbstractCount 0.04 has a bug in
313             # its column-checking code
314 15 50 66     179 if ( ref( $where ) eq 'HASH' and $where->{1} )
315             {
316 0         0 $where = {};
317 0         0 $self->where( {} );
318             }
319            
320 15   33     55 my $per_page = $self->per_page || croak( 'no. of entries per page not specified' );
321 15         135 my $cdbi = $self->_cdbi_app;
322 15         121 my $count = $cdbi->count_search_where( $where, $self->abstract_attr );
323 15   50     155 my $page = $self->page || 1;
324            
325 15         196 $self->total_entries( $count );
326 15         260 $self->entries_per_page( $per_page );
327 15         290 $self->current_page( $page );
328            
329 15 50       237 croak( 'Fewer than one entry per page!' ) if $self->entries_per_page < 1;
330            
331 15 50       187 $self->current_page( $self->first_page ) unless defined $self->current_page;
332 15 50       874 $self->current_page( $self->first_page ) if $self->current_page < $self->first_page;
333 15 50       744 $self->current_page( $self->last_page ) if $self->current_page > $self->last_page;
334             }
335            
336             # SQL::Abstract::_recurse_where eats the WHERE clause
337             #sub where {
338             # my ( $self, $where_ref ) = @_;
339             #
340             # return $self->_where unless $where_ref;
341             #
342             # my $where_copy;
343             #
344             # if ( ref( $where_ref ) eq 'HASH' ) {
345             # $where_copy = { %$where_ref };
346             # }
347             # elsif ( ref( $where_ref ) eq 'ARRAY' )
348             # {
349             # $where_copy = [ @$where_ref ];
350             # }
351             # else
352             # {
353             # die "WHERE clause [$where_ref] must be specified as an ARRAYREF or HASHREF";
354             # }
355             #
356             # # this will get eaten, but the caller's value is now protected
357             # $self->_where( $where_copy );
358             #}
359            
360             =item set_syntax( [ $name || $class || $coderef ] )
361            
362             Changes the syntax used to generate the C or other phrase that restricts
363             the results set to the required page.
364            
365             The syntax is implemented as a method called on the pager, which can be
366             queried to provide the C<$rows> and C<$offset> parameters (see the subclasses
367             included in this distribution).
368            
369             =over 4
370            
371             =item $class
372            
373             A class with a C method.
374            
375             =item $name
376            
377             Name of a class in the C namespace, which has a
378             C method.
379            
380             =item $coderef
381            
382             Will be called as a method on the pager object, so receives the pager as its
383             argument.
384            
385             =item (no args)
386            
387             Called without args, will default to C, which causes
388             L
389             to be used.
390            
391             =back
392            
393             =cut
394            
395             sub set_syntax {
396 19     19 1 84 my ( $proto, $syntax ) = @_;
397            
398             # pick up default from subclass, or load from LimitOffset
399 19   66     64 $syntax ||= $proto->can( 'make_limit' );
400 19   50     46 $syntax ||= 'LimitOffset';
401            
402 19 100       51 if ( ref( $syntax ) eq 'CODE' )
403             {
404 1         12 $proto->_syntax( $syntax );
405 1         53 return $syntax;
406             }
407            
408 18 50       71 my $format_class = $syntax =~ '::' ? $syntax : "Class::DBI::Plugin::Pager::$syntax";
409            
410 18 50       211 $format_class->require || croak "error loading $format_class: $UNIVERSAL::require::ERROR";
411            
412 18   33     995 my $formatter = $format_class->can( 'make_limit' ) || croak "no make_limit method in $format_class";
413            
414 18         68 $proto->_syntax( $formatter );
415            
416 18         165 return $formatter;
417             }
418            
419             =item auto_set_syntax
420            
421             This is called automatically when you call C, and attempts to set the
422             syntax automatically.
423            
424             If you are using a subclass of the pager, this method will not be called.
425            
426             Will C if using Oracle or DB2, since there is no simple syntax for limiting
427             the results set. DB2 has a C keyword, but that seems to apply to a
428             cursor and I don't know if there is a cursor available to the pager. There
429             should probably be others to add to the unsupported list.
430            
431             Supports the following drivers:
432            
433             DRIVER CDBI::P::Pager subclass
434             my %supported = ( pg => 'LimitOffset',
435             mysql => 'LimitOffset', # older versions need LimitXY
436             sqlite => 'LimitOffset', # or LimitYX
437             sqlite2 => 'LimitOffset', # or LimitYX
438             interbase => 'RowsTo',
439             firebird => 'RowsTo',
440             );
441            
442             Older versions of MySQL should use the LimitXY syntax. You'll need to set it
443             manually, either by C, or by passing
444             C 'LimitXY'> to a method call, or call C directly.
445            
446             Any driver not in the supported or unsupported lists defaults to LimitOffset.
447            
448             Any additions to the supported and unsupported lists gratefully received.
449            
450             =cut
451            
452             sub auto_set_syntax {
453 10     10 1 17 my ( $self ) = @_;
454            
455             # not an exhaustive list
456 10         39 my %not_supported = ( oracle => 'Oracle',
457             db2 => 'DB2',
458             );
459            
460             # additions welcome
461 10         70 my %supported = ( pg => 'LimitOffset',
462             mysql => 'LimitOffset', # older versions need LimitXY
463             sqlite => 'LimitOffset', # or LimitYX
464             sqlite2 => 'LimitOffset', # or LimitYX
465             interbase => 'RowsTo',
466             firebird => 'RowsTo',
467             );
468            
469 10         32 my $cdbi = $self->_cdbi_app;
470            
471 10         80 my $driver = lc( $cdbi->__driver );
472            
473 10 50       63 die __PACKAGE__ . " can't build limit clauses for $not_supported{ $driver }"
474             if $not_supported{ $driver };
475            
476             #warn sprintf "Setting syntax to %s for $driver", $supported{ $driver } || 'LimitOffset';
477            
478 10   50     49 $self->set_syntax( $supported{ $driver } || 'LimitOffset' );
479             }
480            
481             1;
482            
483             __END__