Moved inflation to inflate_result in Row.pm
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / ResultSet.pm
1 package DBIx::Class::ResultSet;
2
3 use strict;
4 use warnings;
5 use overload
6         '0+'     => 'count',
7         fallback => 1;
8 use Data::Page;
9
10 =head1 NAME
11
12 DBIx::Class::ResultSet - Responsible for fetching and creating resultset.
13
14 =head1 SYNOPSIS
15
16 my $rs = MyApp::DB::Class->search(registered => 1);
17 my @rows = MyApp::DB::Class->search(foo => 'bar');
18
19 =head1 DESCRIPTION
20
21 The resultset is also known as an iterator. It is responsible for handling
22 queries that may return an arbitrary number of rows, e.g. via C<search>
23 or a C<has_many> relationship.
24
25 =head1 METHODS
26
27 =head2 new($source, \%$attrs)
28
29 The resultset constructor. Takes a source object (usually a DBIx::Class::Table)
30 and an attribute hash (see below for more information on attributes). Does
31 not perform any queries -- these are executed as needed by the other methods.
32
33 =cut
34
35 sub new {
36   my ($class, $source, $attrs) = @_;
37   #use Data::Dumper; warn Dumper(@_);
38   $class = ref $class if ref $class;
39   $attrs = { %{ $attrs || {} } };
40   my %seen;
41   if (!$attrs->{select}) {
42     my @cols = ($attrs->{cols}
43                  ? @{delete $attrs->{cols}}
44                  : $source->result_class->_select_columns);
45     $attrs->{select} = [ map { m/\./ ? $_ : "me.$_" } @cols ];
46   }
47   $attrs->{as} ||= [ map { m/^me\.(.*)$/ ? $1 : $_ } @{$attrs->{select}} ];
48   #use Data::Dumper; warn Dumper(@{$attrs}{qw/select as/});
49   $attrs->{from} ||= [ { 'me' => $source->name } ];
50   if (my $join = delete $attrs->{join}) {
51     foreach my $j (ref $join eq 'ARRAY'
52               ? (@{$join}) : ($join)) {
53       if (ref $j eq 'HASH') {
54         $seen{$_} = 1 foreach keys %$j;
55       } else {
56         $seen{$j} = 1;
57       }
58     }
59     push(@{$attrs->{from}}, $source->result_class->_resolve_join($join, 'me'));
60   }
61   $attrs->{group_by} ||= $attrs->{select} if delete $attrs->{distinct};
62   foreach my $pre (@{delete $attrs->{prefetch} || []}) {
63     push(@{$attrs->{from}}, $source->result_class->_resolve_join($pre, 'me'))
64       unless $seen{$pre};
65     my @pre = 
66       map { "$pre.$_" }
67       $source->result_class->_relationships->{$pre}->{class}->columns;
68     push(@{$attrs->{select}}, @pre);
69     push(@{$attrs->{as}}, @pre);
70   }
71   my $new = {
72     source => $source,
73     cond => $attrs->{where},
74     from => $attrs->{from},
75     count => undef,
76     pager => undef,
77     attrs => $attrs };
78   bless ($new, $class);
79   $new->pager if $attrs->{page};
80   return $new;
81 }
82
83 =head2 search
84
85   my @obj    = $rs->search({ foo => 3 }); # "... WHERE foo = 3"              
86   my $new_rs = $rs->search({ foo => 3 });                                    
87                                                                                 
88 If you need to pass in additional attributes but no additional condition,
89 call it as ->search(undef, \%attrs);
90                                                                                 
91   my @all = $class->search({}, { cols => [qw/foo bar/] }); # "SELECT foo, bar FROM $class_table"
92
93 =cut
94
95 sub search {
96   my $self = shift;
97
98   #use Data::Dumper;warn Dumper(@_);
99
100   my $attrs = { %{$self->{attrs}} };
101   if (@_ > 1 && ref $_[$#_] eq 'HASH') {
102     $attrs = { %{ pop(@_) } };
103   }
104
105   my $where = ((@_ == 1 || ref $_[0] eq "HASH") ? shift : {@_});
106   if (defined $where) {
107     $where = (defined $attrs->{where}
108                 ? { '-and' => [ $where, $attrs->{where} ] }
109                 : $where);
110     $attrs->{where} = $where;
111   }
112
113   my $rs = $self->new($self->{source}, $attrs);
114
115   return (wantarray ? $rs->all : $rs);
116 }
117
118 =head2 search_literal                                                              
119   my @obj    = $rs->search_literal($literal_where_cond, @bind);
120   my $new_rs = $rs->search_literal($literal_where_cond, @bind);
121
122 Pass a literal chunk of SQL to be added to the conditional part of the
123 resultset
124
125 =cut
126                                                          
127 sub search_literal {
128   my ($self, $cond, @vals) = @_;
129   my $attrs = (ref $vals[$#vals] eq 'HASH' ? { %{ pop(@vals) } } : {});
130   $attrs->{bind} = [ @{$self->{attrs}{bind}||[]}, @vals ];
131   return $self->search(\$cond, $attrs);
132 }
133
134 =head2 search_related
135
136   $rs->search_related('relname', $cond?, $attrs?);
137
138 =cut
139
140 sub search_related { }
141
142 =head2 cursor
143
144 Returns a storage-driven cursor to the given resultset.
145
146 =cut
147
148 sub cursor {
149   my ($self) = @_;
150   my ($source, $attrs) = @{$self}{qw/source attrs/};
151   if ($attrs->{page}) {
152     $attrs->{rows} = $self->pager->entries_per_page;
153     $attrs->{offset} = $self->pager->skipped;
154   }
155   return $self->{cursor}
156     ||= $source->storage->select($self->{from}, $attrs->{select},
157           $attrs->{where},$attrs);
158 }
159
160 =head2 search_like                                                               
161                                                                                 
162 Identical to search except defaults to 'LIKE' instead of '=' in condition       
163                                                                                 
164 =cut                                                                            
165
166 sub search_like {
167   my $class    = shift;
168   my $attrs = { };
169   if (@_ > 1 && ref $_[$#_] eq 'HASH') {
170     $attrs = pop(@_);
171   }
172   my $query    = ref $_[0] eq "HASH" ? { %{shift()} }: {@_};
173   $query->{$_} = { 'like' => $query->{$_} } for keys %$query;
174   return $class->search($query, { %$attrs });
175 }
176
177 =head2 slice($first, $last)
178
179 Returns a subset of elements from the resultset.
180
181 =cut
182
183 sub slice {
184   my ($self, $min, $max) = @_;
185   my $attrs = { %{ $self->{attrs} || {} } };
186   $self->{source}->result_class->throw("Can't slice without where") unless $attrs->{where};
187   $attrs->{offset} = $min;
188   $attrs->{rows} = ($max ? ($max - $min + 1) : 1);
189   my $slice = $self->new($self->{source}, $attrs);
190   return (wantarray ? $slice->all : $slice);
191 }
192
193 =head2 next 
194
195 Returns the next element in the resultset (undef is there is none).
196
197 =cut
198
199 sub next {
200   my ($self) = @_;
201   my @row = $self->cursor->next;
202   return unless (@row);
203   return $self->_construct_object(@row);
204 }
205
206 sub _construct_object {
207   my ($self, @row) = @_;
208   my @cols = @{ $self->{attrs}{as} };
209   #warn "@cols -> @row";
210   my (%me, %pre);
211   foreach my $col (@cols) {
212     if ($col =~ /([^\.]+)\.([^\.]+)/) {
213       $pre{$1}{$2} = shift @row;
214     } else {
215       $me{$col} = shift @row;
216     }
217   }
218   my $new = $self->{source}->result_class->inflate_result(\%me, \%pre);
219   $new = $self->{attrs}{record_filter}->($new)
220     if exists $self->{attrs}{record_filter};
221   return $new;
222 }
223
224 =head2 count
225
226 Performs an SQL C<COUNT> with the same query as the resultset was built
227 with to find the number of elements. If passed arguments, does a search
228 on the resultset and counts the results of that.
229
230 =cut
231
232 sub count {
233   my $self = shift;
234   return $self->search(@_)->count if @_ && defined $_[0];
235   die "Unable to ->count with a GROUP BY" if defined $self->{attrs}{group_by};
236   unless ($self->{count}) {
237     my $attrs = { %{ $self->{attrs} },
238                   select => { 'count' => '*' },
239                   as => [ 'count' ] };
240     # offset and order by are not needed to count, page, join and prefetch
241     # will get in the way (add themselves to from again ...)
242     delete $attrs->{$_} for qw/offset order_by page join prefetch/;
243         
244     my @cols = 'COUNT(*)';
245     ($self->{count}) = $self->search(undef, $attrs)->cursor->next;
246   }
247   return 0 unless $self->{count};
248   return $self->{pager}->entries_on_this_page if ($self->{pager});
249   return ( $self->{attrs}->{rows} && $self->{attrs}->{rows} < $self->{count} ) 
250     ? $self->{attrs}->{rows} 
251     : $self->{count};
252 }
253
254 =head2 count_literal
255
256 Calls search_literal with the passed arguments, then count.
257
258 =cut
259
260 sub count_literal { shift->search_literal(@_)->count; }
261
262 =head2 all
263
264 Returns all elements in the resultset. Called implictly if the resultset
265 is returned in list context.
266
267 =cut
268
269 sub all {
270   my ($self) = @_;
271   return map { $self->_construct_object(@$_); }
272            $self->cursor->all;
273 }
274
275 =head2 reset
276
277 Resets the resultset's cursor, so you can iterate through the elements again.
278
279 =cut
280
281 sub reset {
282   my ($self) = @_;
283   $self->cursor->reset;
284   return $self;
285 }
286
287 =head2 first
288
289 Resets the resultset and returns the first element.
290
291 =cut
292
293 sub first {
294   return $_[0]->reset->next;
295 }
296
297 =head2 delete
298
299 Deletes all elements in the resultset.
300
301 =cut
302
303 sub delete {
304   my ($self) = @_;
305   $_->delete for $self->all;
306   return 1;
307 }
308
309 *delete_all = \&delete; # Yeah, yeah, yeah ...
310
311 =head2 pager
312
313 Returns a L<Data::Page> object for the current resultset. Only makes
314 sense for queries with page turned on.
315
316 =cut
317
318 sub pager {
319   my ($self) = @_;
320   my $attrs = $self->{attrs};
321   delete $attrs->{offset};
322   my $rows_per_page = delete $attrs->{rows} || 10;
323   $self->{pager} ||= Data::Page->new(
324     $self->count, $rows_per_page, $attrs->{page} || 1);
325   $attrs->{rows} = $rows_per_page;
326   return $self->{pager};
327 }
328
329 =head2 page($page_num)
330
331 Returns a new resultset for the specified page.
332
333 =cut
334
335 sub page {
336   my ($self, $page) = @_;
337   my $attrs = $self->{attrs};
338   $attrs->{page} = $page;
339   return $self->new($self->{source}, $attrs);
340 }
341
342 =head1 Attributes
343
344 The resultset takes various attributes that modify its behavior.
345 Here's an overview of them:
346
347 =head2 order_by
348
349 Which column(s) to order the results by. This is currently passed
350 through directly to SQL, so you can give e.g. C<foo DESC> for a 
351 descending order.
352
353 =head2 cols (arrayref)
354
355 Shortcut to request a particular set of columns to be retrieved - adds
356 'me.' onto the start of any column without a '.' in it and sets 'select'
357 from that, then auto-populates 'as' from 'select' as normal
358
359 =head2 select (arrayref)
360
361 Indicates which columns should be selected from the storage
362
363 =head2 as (arrayref)
364
365 Indicates column names for object inflation
366
367 =head2 join
368
369 Contains a list of relationships that should be joined for this query. Can also 
370 contain a hash reference to refer to that relation's relations. So, if one column
371 in your class C<belongs_to> foo and another C<belongs_to> bar, you can do
372 C<< join => [qw/ foo bar /] >> to join both (and e.g. use them for C<order_by>).
373 If a foo contains many margles and you want to join those too, you can do
374 C<< join => { foo => 'margle' } >>. If you want to fetch the columns from the
375 related table as well, see C<prefetch> below.
376
377 =head2 prefetch
378
379 Contains a list of relationships that should be fetched along with the main 
380 query (when they are accessed afterwards they will have already been
381 "prefetched"). This is useful for when you know you will need the related
382 object(s), because it saves a query. Currently limited to prefetching
383 one relationship deep, so unlike C<join>, prefetch must be an arrayref.
384
385 =head2 from 
386
387 This attribute can contain a arrayref of elements. Each element can be another
388 arrayref, to nest joins, or it can be a hash which represents the two sides
389 of the join. 
390
391 NOTE: Use this on your own risk. This allows you to shoot your foot off!
392
393 =head2 page
394
395 For a paged resultset, specifies which page to retrieve. Leave unset
396 for an unpaged resultset.
397
398 =head2 rows
399
400 For a paged resultset, how many rows per page
401
402 =head2 group_by
403
404 A list of columns to group by (note that 'count' doesn't work on grouped
405 resultsets)
406
407 =head2 distinct
408
409 Set to 1 to group by all columns
410
411 =cut
412
413 1;