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