1 package FS::PagedSearch;
4 use vars qw($DEBUG $default_limit @EXPORT_OK);
5 use base qw( Exporter );
6 use FS::Record qw(qsearch dbdef);
12 @EXPORT_OK = 'psearch';
16 FS::PagedSearch - Iterator for querying large data sets
20 use FS::PagedSearch qw(psearch);
22 my $search = psearch('table', { field => 'value' ... });
23 $search->limit(100); #optional
24 while ( my $row = $search->fetch ) {
32 =item psearch ARGUMENTS
34 A wrapper around L<FS::Record::qsearch>. Accepts all the same arguments
35 as qsearch, except for the arrayref union query mode, and returns an
36 FS::PagedSearch object to access the rows of the query one at a time.
37 If the query doesn't contain an ORDER BY clause already, it will be ordered
38 by the table's primary key.
43 # deep-copy qsearch args
45 if ( ref($_[0]) eq 'ARRAY' ) {
46 die "union query not supported with psearch"; #yet
48 elsif ( ref($_[0]) eq 'HASH' ) {
61 warn Dumper($q) if $DEBUG > 1;
64 my $dbdef = dbdef->table($q->{table});
65 # qsearch just appends order_by to extra_sql, so do that ourselves
66 $q->{extra_sql} ||= '';
67 $q->{extra_sql} .= ' '.$q->{order_by} if $q->{order_by};
69 # and impose an ordering if needed
70 if ( not $q->{extra_sql} =~ /order by/i ) {
71 $q->{extra_sql} .= ' ORDER BY '.$dbdef->primary_key;
73 # and then we'll use order_by for LIMIT/OFFSET
79 limit => $default_limit,
82 bless $self, 'FS::PagedSearch';
95 Fetch the next row from the search results and remove it from the buffer.
96 Returns undef if there are no more rows.
102 my $b = $self->{buffer};
103 $self->refill if @$b == 0;
104 $self->{offset} += $self->{increment} if @$b;
110 Add ROWS to the offset counter. This won't cause rows to be skipped in the
111 current buffer but will affect the starting point of the next refill.
118 $self->{offset} += $r;
121 =item limit [ VALUE ]
123 Set/get the number of rows to retrieve per page. The default is 100.
129 my $new_limit = shift;
130 if ( defined($new_limit) ) {
131 $self->{limit} = $new_limit;
136 =item increment [ VALUE ]
138 Set/get the number of rows to increment the offset for each row that's
139 retrieved. Defaults to 1. If the rows are being modified in a way that
140 removes them from the result set of the query, it's probably wise to set
141 this to zero. Setting it to anything else is probably nonsense.
148 if ( defined($new_inc) ) {
149 $self->{increment} = $new_inc;
157 Run the query, skipping a number of rows set by the row offset, and replace
158 the contents of the buffer with the result. If there are no more rows,
159 this will just empty the buffer. Called automatically as needed; don't call
166 my $b = $self->{buffer};
167 warn "refilling (limit ".$self->{limit}.", offset ".$self->{offset}.")\n"
169 warn "discarding ".scalar(@$b)." rows\n" if $DEBUG and @$b;
170 if ( $self->{limit} > 0 ) {
171 $self->{query}->{order_by} = 'LIMIT ' . $self->{limit} .
172 ' OFFSET ' . $self->{offset};
174 @$b = qsearch( $self->{query} );
175 my $rows = scalar @$b;
176 warn "$rows returned\n" if $DEBUG;
187 L<FS::Cursor> is an eventual replacement for this.