Fixed odd ->search bug caused by S::A brain damage
[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;
ea20d0fd 9use Storable;
89c0a5a2 10
ee38fa40 11=head1 NAME
12
bfab575a 13DBIx::Class::ResultSet - Responsible for fetching and creating resultset.
ee38fa40 14
bfab575a 15=head1 SYNOPSIS
ee38fa40 16
bfab575a 17my $rs = MyApp::DB::Class->search(registered => 1);
18my @rows = MyApp::DB::Class->search(foo => 'bar');
ee38fa40 19
20=head1 DESCRIPTION
21
bfab575a 22The resultset is also known as an iterator. It is responsible for handling
23queries that may return an arbitrary number of rows, e.g. via C<search>
24or a C<has_many> relationship.
ee38fa40 25
26=head1 METHODS
27
976f3686 28=head2 new($source, \%$attrs)
ee38fa40 29
976f3686 30The resultset constructor. Takes a source object (usually a DBIx::Class::Table)
31and an attribute hash (see below for more information on attributes). Does
32not perform any queries -- these are executed as needed by the other methods.
ee38fa40 33
34=cut
35
89c0a5a2 36sub new {
fea3d045 37 my $class = shift;
f9db5527 38 return $class->new_result(@_) if ref $class;
fea3d045 39 my ($source, $attrs) = @_;
89c0a5a2 40 #use Data::Dumper; warn Dumper(@_);
ea20d0fd 41 $attrs = Storable::dclone($attrs || {}); # { %{ $attrs || {} } };
c7ce65e6 42 my %seen;
6aeb9185 43 my $alias = ($attrs->{alias} ||= 'me');
976f3686 44 if (!$attrs->{select}) {
45 my @cols = ($attrs->{cols}
46 ? @{delete $attrs->{cols}}
47 : $source->result_class->_select_columns);
6aeb9185 48 $attrs->{select} = [ map { m/\./ ? $_ : "${alias}.$_" } @cols ];
976f3686 49 }
6aeb9185 50 $attrs->{as} ||= [ map { m/^$alias\.(.*)$/ ? $1 : $_ } @{$attrs->{select}} ];
976f3686 51 #use Data::Dumper; warn Dumper(@{$attrs}{qw/select as/});
fea3d045 52 $attrs->{from} ||= [ { $alias => $source->from } ];
b52e9bf8 53 if (my $join = delete $attrs->{join}) {
54 foreach my $j (ref $join eq 'ARRAY'
55 ? (@{$join}) : ($join)) {
c7ce65e6 56 if (ref $j eq 'HASH') {
57 $seen{$_} = 1 foreach keys %$j;
58 } else {
59 $seen{$j} = 1;
60 }
61 }
8452e496 62 push(@{$attrs->{from}}, $source->resolve_join($join, $attrs->{alias}));
c7ce65e6 63 }
54540863 64 $attrs->{group_by} ||= $attrs->{select} if delete $attrs->{distinct};
b52e9bf8 65 foreach my $pre (@{delete $attrs->{prefetch} || []}) {
8452e496 66 push(@{$attrs->{from}}, $source->resolve_join($pre, $attrs->{alias}))
c7ce65e6 67 unless $seen{$pre};
976f3686 68 my @pre =
c7ce65e6 69 map { "$pre.$_" }
f9db5527 70 $source->related_source($pre)->columns;
976f3686 71 push(@{$attrs->{select}}, @pre);
72 push(@{$attrs->{as}}, @pre);
fef5d100 73 }
6aeb9185 74 if ($attrs->{page}) {
75 $attrs->{rows} ||= 10;
76 $attrs->{offset} ||= 0;
77 $attrs->{offset} += ($attrs->{rows} * ($attrs->{page} - 1));
78 }
89c0a5a2 79 my $new = {
cda04c3a 80 source => $source,
89c0a5a2 81 cond => $attrs->{where},
0a3c5b43 82 from => $attrs->{from},
3c5b25c5 83 count => undef,
93b004d3 84 page => delete $attrs->{page},
3c5b25c5 85 pager => undef,
89c0a5a2 86 attrs => $attrs };
2f5911b2 87 bless ($new, $class);
9229f20a 88 return $new;
89c0a5a2 89}
90
bfab575a 91=head2 search
0a3c5b43 92
6009260a 93 my @obj = $rs->search({ foo => 3 }); # "... WHERE foo = 3"
94 my $new_rs = $rs->search({ foo => 3 });
95
96If you need to pass in additional attributes but no additional condition,
97call it as ->search(undef, \%attrs);
98
99 my @all = $class->search({}, { cols => [qw/foo bar/] }); # "SELECT foo, bar FROM $class_table"
0a3c5b43 100
101=cut
102
103sub search {
104 my $self = shift;
105
6009260a 106 #use Data::Dumper;warn Dumper(@_);
107
0a3c5b43 108 my $attrs = { %{$self->{attrs}} };
109 if (@_ > 1 && ref $_[$#_] eq 'HASH') {
6aeb9185 110 $attrs = { %$attrs, %{ pop(@_) } };
0a3c5b43 111 }
112
6aeb9185 113 my $where = (@_ ? ((@_ == 1 || ref $_[0] eq "HASH") ? shift : {@_}) : undef());
0a3c5b43 114 if (defined $where) {
115 $where = (defined $attrs->{where}
ad3d2d7c 116 ? { '-and' =>
117 [ map { ref $_ eq 'ARRAY' ? [ -or => $_ ] : $_ }
118 $where, $attrs->{where} ] }
0a3c5b43 119 : $where);
120 $attrs->{where} = $where;
121 }
122
fea3d045 123 my $rs = (ref $self)->new($self->{source}, $attrs);
0a3c5b43 124
125 return (wantarray ? $rs->all : $rs);
126}
127
bfab575a 128=head2 search_literal
6009260a 129 my @obj = $rs->search_literal($literal_where_cond, @bind);
130 my $new_rs = $rs->search_literal($literal_where_cond, @bind);
131
132Pass a literal chunk of SQL to be added to the conditional part of the
133resultset
134
bfab575a 135=cut
136
6009260a 137sub search_literal {
138 my ($self, $cond, @vals) = @_;
139 my $attrs = (ref $vals[$#vals] eq 'HASH' ? { %{ pop(@vals) } } : {});
140 $attrs->{bind} = [ @{$self->{attrs}{bind}||[]}, @vals ];
141 return $self->search(\$cond, $attrs);
142}
0a3c5b43 143
716b3d29 144=head2 find(@colvalues), find(\%cols)
145
146Finds a row based on its primary key(s).
147
148=cut
149
150sub find {
151 my ($self, @vals) = @_;
152 my $attrs = (@vals > 1 && ref $vals[$#vals] eq 'HASH' ? pop(@vals) : {});
153 my @pk = $self->{source}->primary_columns;
154 #use Data::Dumper; warn Dumper($attrs, @vals, @pk);
155 $self->{source}->result_class->throw( "Can't find unless primary columns are defined" )
156 unless @pk;
157 my $query;
158 if (ref $vals[0] eq 'HASH') {
159 $query = $vals[0];
160 } elsif (@pk == @vals) {
161 $query = {};
162 @{$query}{@pk} = @vals;
163 } else {
164 $query = {@vals};
165 }
166 #warn Dumper($query);
167 # Useless -> disabled
168 #$self->{source}->result_class->throw( "Can't find unless all primary keys are specified" )
169 # unless (keys %$query >= @pk); # If we check 'em we run afoul of uc/lc
170 # column names etc. Not sure what to do yet
171 return $self->search($query)->next;
172}
173
b52e9bf8 174=head2 search_related
175
176 $rs->search_related('relname', $cond?, $attrs?);
177
178=cut
179
6aeb9185 180sub search_related {
181 my ($self, $rel, @rest) = @_;
f9db5527 182 my $rel_obj = $self->{source}->relationship_info($rel);
6aeb9185 183 $self->{source}->result_class->throw(
184 "No such relationship ${rel} in search_related")
185 unless $rel_obj;
6aeb9185 186 my $rs = $self->search(undef, { join => $rel });
ea20d0fd 187 return $self->{source}->schema->resultset($rel_obj->{class}
188 )->search( undef,
189 { %{$rs->{attrs}},
190 alias => $rel,
191 select => undef(),
192 as => undef() }
6aeb9185 193 )->search(@rest);
194}
b52e9bf8 195
bfab575a 196=head2 cursor
ee38fa40 197
bfab575a 198Returns a storage-driven cursor to the given resultset.
ee38fa40 199
200=cut
201
73f58123 202sub cursor {
203 my ($self) = @_;
2f5911b2 204 my ($source, $attrs) = @{$self}{qw/source attrs/};
6aeb9185 205 $attrs = { %$attrs };
73f58123 206 return $self->{cursor}
976f3686 207 ||= $source->storage->select($self->{from}, $attrs->{select},
73f58123 208 $attrs->{where},$attrs);
209}
210
bfab575a 211=head2 search_like
58a4bd18 212
213Identical to search except defaults to 'LIKE' instead of '=' in condition
214
215=cut
216
217sub search_like {
218 my $class = shift;
219 my $attrs = { };
220 if (@_ > 1 && ref $_[$#_] eq 'HASH') {
221 $attrs = pop(@_);
222 }
223 my $query = ref $_[0] eq "HASH" ? { %{shift()} }: {@_};
224 $query->{$_} = { 'like' => $query->{$_} } for keys %$query;
225 return $class->search($query, { %$attrs });
226}
227
bfab575a 228=head2 slice($first, $last)
ee38fa40 229
bfab575a 230Returns a subset of elements from the resultset.
ee38fa40 231
232=cut
233
89c0a5a2 234sub slice {
235 my ($self, $min, $max) = @_;
236 my $attrs = { %{ $self->{attrs} || {} } };
6aeb9185 237 $attrs->{offset} ||= 0;
238 $attrs->{offset} += $min;
89c0a5a2 239 $attrs->{rows} = ($max ? ($max - $min + 1) : 1);
fea3d045 240 my $slice = (ref $self)->new($self->{source}, $attrs);
89c0a5a2 241 return (wantarray ? $slice->all : $slice);
242}
243
bfab575a 244=head2 next
ee38fa40 245
bfab575a 246Returns the next element in the resultset (undef is there is none).
ee38fa40 247
248=cut
249
89c0a5a2 250sub next {
251 my ($self) = @_;
73f58123 252 my @row = $self->cursor->next;
89c0a5a2 253 return unless (@row);
c7ce65e6 254 return $self->_construct_object(@row);
255}
256
257sub _construct_object {
258 my ($self, @row) = @_;
976f3686 259 my @cols = @{ $self->{attrs}{as} };
260 #warn "@cols -> @row";
b52e9bf8 261 my (%me, %pre);
262 foreach my $col (@cols) {
263 if ($col =~ /([^\.]+)\.([^\.]+)/) {
6aeb9185 264 $pre{$1}[0]{$2} = shift @row;
b52e9bf8 265 } else {
266 $me{$col} = shift @row;
c7ce65e6 267 }
c7ce65e6 268 }
b52e9bf8 269 my $new = $self->{source}->result_class->inflate_result(\%me, \%pre);
33ce49d6 270 $new = $self->{attrs}{record_filter}->($new)
271 if exists $self->{attrs}{record_filter};
272 return $new;
89c0a5a2 273}
274
bfab575a 275=head2 count
ee38fa40 276
bfab575a 277Performs an SQL C<COUNT> with the same query as the resultset was built
6009260a 278with to find the number of elements. If passed arguments, does a search
279on the resultset and counts the results of that.
ee38fa40 280
281=cut
282
89c0a5a2 283sub count {
6009260a 284 my $self = shift;
285 return $self->search(@_)->count if @_ && defined $_[0];
54540863 286 die "Unable to ->count with a GROUP BY" if defined $self->{attrs}{group_by};
6aeb9185 287 unless (defined $self->{count}) {
976f3686 288 my $attrs = { %{ $self->{attrs} },
54540863 289 select => { 'count' => '*' },
290 as => [ 'count' ] };
ea20d0fd 291 # offset, order by and page are not needed to count. record_filter is cdbi
292 delete $attrs->{$_} for qw/rows offset order_by page pager record_filter/;
3c5b25c5 293
fea3d045 294 ($self->{count}) = (ref $self)->new($self->{source}, $attrs)->cursor->next;
3c5b25c5 295 }
296 return 0 unless $self->{count};
6aeb9185 297 my $count = $self->{count};
298 $count -= $self->{attrs}{offset} if $self->{attrs}{offset};
299 $count = $self->{attrs}{rows} if
300 ($self->{attrs}{rows} && $self->{attrs}{rows} < $count);
301 return $count;
89c0a5a2 302}
303
bfab575a 304=head2 count_literal
6009260a 305
bfab575a 306Calls search_literal with the passed arguments, then count.
6009260a 307
308=cut
309
310sub count_literal { shift->search_literal(@_)->count; }
311
bfab575a 312=head2 all
ee38fa40 313
bfab575a 314Returns all elements in the resultset. Called implictly if the resultset
315is returned in list context.
ee38fa40 316
317=cut
318
89c0a5a2 319sub all {
320 my ($self) = @_;
c7ce65e6 321 return map { $self->_construct_object(@$_); }
73f58123 322 $self->cursor->all;
89c0a5a2 323}
324
bfab575a 325=head2 reset
ee38fa40 326
bfab575a 327Resets the resultset's cursor, so you can iterate through the elements again.
ee38fa40 328
329=cut
330
89c0a5a2 331sub reset {
332 my ($self) = @_;
73f58123 333 $self->cursor->reset;
89c0a5a2 334 return $self;
335}
336
bfab575a 337=head2 first
ee38fa40 338
bfab575a 339Resets the resultset and returns the first element.
ee38fa40 340
341=cut
342
89c0a5a2 343sub first {
344 return $_[0]->reset->next;
345}
346
bfab575a 347=head2 delete
ee38fa40 348
38659261 349Deletes all elements in the resultset.
ee38fa40 350
351=cut
352
28927b50 353sub delete {
89c0a5a2 354 my ($self) = @_;
355 $_->delete for $self->all;
356 return 1;
357}
358
28927b50 359*delete_all = \&delete; # Yeah, yeah, yeah ...
360
bfab575a 361=head2 pager
ee38fa40 362
363Returns a L<Data::Page> object for the current resultset. Only makes
364sense for queries with page turned on.
365
366=cut
367
3c5b25c5 368sub pager {
369 my ($self) = @_;
370 my $attrs = $self->{attrs};
93b004d3 371 die "Can't create pager for non-paged rs" unless $self->{page};
6aeb9185 372 $attrs->{rows} ||= 10;
373 $self->count;
374 return $self->{pager} ||= Data::Page->new(
93b004d3 375 $self->{count}, $attrs->{rows}, $self->{page});
3c5b25c5 376}
377
bfab575a 378=head2 page($page_num)
ee38fa40 379
bfab575a 380Returns a new resultset for the specified page.
ee38fa40 381
382=cut
383
3c5b25c5 384sub page {
385 my ($self, $page) = @_;
6aeb9185 386 my $attrs = { %{$self->{attrs}} };
3c5b25c5 387 $attrs->{page} = $page;
fea3d045 388 return (ref $self)->new($self->{source}, $attrs);
389}
390
391=head2 new_result(\%vals)
392
393Creates a result in the resultset's result class
394
395=cut
396
397sub new_result {
398 my ($self, $values) = @_;
399 $self->{source}->result_class->throw( "new_result needs a hash" )
400 unless (ref $values eq 'HASH');
401 $self->{source}->result_class->throw( "Can't abstract implicit construct, condition not a hash" )
402 if ($self->{cond} && !(ref $self->{cond} eq 'HASH'));
403 my %new = %$values;
404 my $alias = $self->{attrs}{alias};
405 foreach my $key (keys %{$self->{cond}||{}}) {
406 $new{$1} = $self->{cond}{$key} if ($key =~ m/^(?:$alias\.)?([^\.]+)$/);
407 }
408 return $self->{source}->result_class->new(\%new);
409}
410
411=head2 create(\%vals)
412
413Inserts a record into the resultset and returns the object
414
415Effectively a shortcut for ->new_result(\%vals)->insert
416
417=cut
418
419sub create {
420 my ($self, $attrs) = @_;
421 $self->{source}->result_class->throw( "create needs a hashref" ) unless ref $attrs eq 'HASH';
422 return $self->new_result($attrs)->insert;
3c5b25c5 423}
424
c2b15ecc 425=head2 find_or_create(\%vals)
426
427 $class->find_or_create({ key => $val, ... });
428
429Searches for a record matching the search condition; if it doesn't find one,
430creates one and returns that instead.
431
432=cut
433
434sub find_or_create {
435 my $self = shift;
436 my $hash = ref $_[0] eq "HASH" ? shift: {@_};
437 my $exists = $self->find($hash);
438 return defined($exists) ? $exists : $self->create($hash);
439}
440
40dbc108 441=head1 ATTRIBUTES
076652e8 442
bfab575a 443The resultset takes various attributes that modify its behavior.
444Here's an overview of them:
445
446=head2 order_by
076652e8 447
bfab575a 448Which column(s) to order the results by. This is currently passed
449through directly to SQL, so you can give e.g. C<foo DESC> for a
450descending order.
076652e8 451
976f3686 452=head2 cols (arrayref)
453
454Shortcut to request a particular set of columns to be retrieved - adds
455'me.' onto the start of any column without a '.' in it and sets 'select'
456from that, then auto-populates 'as' from 'select' as normal
457
458=head2 select (arrayref)
459
460Indicates which columns should be selected from the storage
461
462=head2 as (arrayref)
076652e8 463
976f3686 464Indicates column names for object inflation
ee38fa40 465
bfab575a 466=head2 join
ee38fa40 467
bfab575a 468Contains a list of relationships that should be joined for this query. Can also
469contain a hash reference to refer to that relation's relations. So, if one column
470in your class C<belongs_to> foo and another C<belongs_to> bar, you can do
471C<< join => [qw/ foo bar /] >> to join both (and e.g. use them for C<order_by>).
472If a foo contains many margles and you want to join those too, you can do
473C<< join => { foo => 'margle' } >>. If you want to fetch the columns from the
474related table as well, see C<prefetch> below.
ee38fa40 475
bfab575a 476=head2 prefetch
ee38fa40 477
bfab575a 478Contains a list of relationships that should be fetched along with the main
479query (when they are accessed afterwards they will have already been
480"prefetched"). This is useful for when you know you will need the related
481object(s), because it saves a query. Currently limited to prefetching
482one relationship deep, so unlike C<join>, prefetch must be an arrayref.
ee38fa40 483
bfab575a 484=head2 from
ee38fa40 485
bfab575a 486This attribute can contain a arrayref of elements. Each element can be another
ee38fa40 487arrayref, to nest joins, or it can be a hash which represents the two sides
488of the join.
489
bfab575a 490NOTE: Use this on your own risk. This allows you to shoot your foot off!
ee38fa40 491
bfab575a 492=head2 page
076652e8 493
bfab575a 494For a paged resultset, specifies which page to retrieve. Leave unset
495for an unpaged resultset.
076652e8 496
bfab575a 497=head2 rows
076652e8 498
bfab575a 499For a paged resultset, how many rows per page
076652e8 500
54540863 501=head2 group_by
502
503A list of columns to group by (note that 'count' doesn't work on grouped
504resultsets)
505
506=head2 distinct
507
508Set to 1 to group by all columns
509
bfab575a 510=cut
076652e8 511
89c0a5a2 5121;