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