X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FDBIx%2FClass%2FResultSet.pm;h=e52e17922b675e5a64996bbaab6e6558119976ee;hb=54540863adce71e931685a37d33e37650e5feb5e;hp=4e69104bc9479ff28057407c0633e9e365f18437;hpb=0a3c5b435bb12d9cf11a403cb2933d106e8a0374;p=dbsrgits%2FDBIx-Class-Historic.git diff --git a/lib/DBIx/Class/ResultSet.pm b/lib/DBIx/Class/ResultSet.pm index 4e69104..e52e179 100644 --- a/lib/DBIx/Class/ResultSet.pm +++ b/lib/DBIx/Class/ResultSet.pm @@ -9,35 +9,44 @@ use Data::Page; =head1 NAME -DBIX::Class::ResultSet - Responsible for fetching and creating resultset. +DBIx::Class::ResultSet - Responsible for fetching and creating resultset. -=head1 SYNOPSIS; +=head1 SYNOPSIS -$rs=MyApp::DB::Class->search(registered=>1); +my $rs = MyApp::DB::Class->search(registered => 1); +my @rows = MyApp::DB::Class->search(foo => 'bar'); =head1 DESCRIPTION -The resultset is also known as an iterator. +The resultset is also known as an iterator. It is responsible for handling +queries that may return an arbitrary number of rows, e.g. via C +or a C relationship. =head1 METHODS -=over 4 +=head2 new($source, \%$attrs) -=item new - -The resultset constructor. Takes a db class and an -attribute hash (see below for more info on attributes) +The resultset constructor. Takes a source object (usually a DBIx::Class::Table) +and an attribute hash (see below for more information on attributes). Does +not perform any queries -- these are executed as needed by the other methods. =cut sub new { - my ($class, $db_class, $attrs) = @_; + my ($class, $source, $attrs) = @_; #use Data::Dumper; warn Dumper(@_); $class = ref $class if ref $class; $attrs = { %{ $attrs || {} } }; my %seen; - $attrs->{cols} ||= [ map { "me.$_" } $db_class->_select_columns ]; - $attrs->{from} ||= [ { 'me' => $db_class->_table_name } ]; + if (!$attrs->{select}) { + my @cols = ($attrs->{cols} + ? @{delete $attrs->{cols}} + : $source->result_class->_select_columns); + $attrs->{select} = [ map { m/\./ ? $_ : "me.$_" } @cols ]; + } + $attrs->{as} ||= [ map { m/^me\.(.*)$/ ? $1 : $_ } @{$attrs->{select}} ]; + #use Data::Dumper; warn Dumper(@{$attrs}{qw/select as/}); + $attrs->{from} ||= [ { 'me' => $source->name } ]; if ($attrs->{join}) { foreach my $j (ref $attrs->{join} eq 'ARRAY' ? (@{$attrs->{join}}) : ($attrs->{join})) { @@ -47,43 +56,53 @@ sub new { $seen{$j} = 1; } } - push(@{$attrs->{from}}, $db_class->_resolve_join($attrs->{join}, 'me')); + push(@{$attrs->{from}}, $source->result_class->_resolve_join($attrs->{join}, 'me')); } + $attrs->{group_by} ||= $attrs->{select} if delete $attrs->{distinct}; foreach my $pre (@{$attrs->{prefetch} || []}) { - push(@{$attrs->{from}}, $db_class->_resolve_join($pre, 'me')) + push(@{$attrs->{from}}, $source->result_class->_resolve_join($pre, 'me')) unless $seen{$pre}; - push(@{$attrs->{cols}}, + my @pre = map { "$pre.$_" } - $db_class->_relationships->{$pre}->{class}->_select_columns); + $source->result_class->_relationships->{$pre}->{class}->columns; + push(@{$attrs->{select}}, @pre); + push(@{$attrs->{as}}, @pre); } my $new = { - source => $db_class, - cols => $attrs->{cols}, + source => $source, cond => $attrs->{where}, from => $attrs->{from}, count => undef, pager => undef, attrs => $attrs }; bless ($new, $class); - $new->pager if ($attrs->{page}); + $new->pager if $attrs->{page}; return $new; } -=item search +=head2 search -Runs a search against the current resultset. + my @obj = $rs->search({ foo => 3 }); # "... WHERE foo = 3" + my $new_rs = $rs->search({ foo => 3 }); + +If you need to pass in additional attributes but no additional condition, +call it as ->search(undef, \%attrs); + + my @all = $class->search({}, { cols => [qw/foo bar/] }); # "SELECT foo, bar FROM $class_table" =cut sub search { my $self = shift; + #use Data::Dumper;warn Dumper(@_); + my $attrs = { %{$self->{attrs}} }; if (@_ > 1 && ref $_[$#_] eq 'HASH') { $attrs = { %{ pop(@_) } }; } - my $where = (@_ == 1 || ref $_[0] eq "HASH" ? shift: {@_}); + my $where = ((@_ == 1 || ref $_[0] eq "HASH") ? shift : {@_}); if (defined $where) { $where = (defined $attrs->{where} ? { '-and' => [ $where, $attrs->{where} ] } @@ -96,10 +115,25 @@ sub search { return (wantarray ? $rs->all : $rs); } +=head2 search_literal + my @obj = $rs->search_literal($literal_where_cond, @bind); + my $new_rs = $rs->search_literal($literal_where_cond, @bind); + +Pass a literal chunk of SQL to be added to the conditional part of the +resultset -=item cursor +=cut + +sub search_literal { + my ($self, $cond, @vals) = @_; + my $attrs = (ref $vals[$#vals] eq 'HASH' ? { %{ pop(@vals) } } : {}); + $attrs->{bind} = [ @{$self->{attrs}{bind}||[]}, @vals ]; + return $self->search(\$cond, $attrs); +} -Return a storage driven cursor to the given resultset. +=head2 cursor + +Returns a storage-driven cursor to the given resultset. =cut @@ -111,29 +145,46 @@ sub cursor { $attrs->{offset} = $self->pager->skipped; } return $self->{cursor} - ||= $source->storage->select($self->{from}, $self->{cols}, + ||= $source->storage->select($self->{from}, $attrs->{select}, $attrs->{where},$attrs); } -=item slice +=head2 search_like + +Identical to search except defaults to 'LIKE' instead of '=' in condition + +=cut -return a number of elements from the given resultset. +sub search_like { + my $class = shift; + my $attrs = { }; + if (@_ > 1 && ref $_[$#_] eq 'HASH') { + $attrs = pop(@_); + } + my $query = ref $_[0] eq "HASH" ? { %{shift()} }: {@_}; + $query->{$_} = { 'like' => $query->{$_} } for keys %$query; + return $class->search($query, { %$attrs }); +} + +=head2 slice($first, $last) + +Returns a subset of elements from the resultset. =cut sub slice { my ($self, $min, $max) = @_; my $attrs = { %{ $self->{attrs} || {} } }; - $self->{source}->throw("Can't slice without where") unless $attrs->{where}; + $self->{source}->result_class->throw("Can't slice without where") unless $attrs->{where}; $attrs->{offset} = $min; $attrs->{rows} = ($max ? ($max - $min + 1) : 1); my $slice = $self->new($self->{source}, $attrs); return (wantarray ? $slice->all : $slice); } -=item next +=head2 next -Returns the next element in this resultset. +Returns the next element in the resultset (undef is there is none). =cut @@ -146,22 +197,22 @@ sub next { sub _construct_object { my ($self, @row) = @_; - my @cols = @{ $self->{attrs}{cols} }; - s/^me\.// for @cols; + my @cols = @{ $self->{attrs}{as} }; + #warn "@cols -> @row"; @cols = grep { /\(/ or ! /\./ } @cols; my $new; unless ($self->{attrs}{prefetch}) { - $new = $self->{source}->_row_to_object(\@cols, \@row); + $new = $self->{source}->result_class->_row_to_object(\@cols, \@row); } else { my @main = splice(@row, 0, scalar @cols); - $new = $self->{source}->_row_to_object(\@cols, \@main); + $new = $self->{source}->result_class->_row_to_object(\@cols, \@main); PRE: foreach my $pre (@{$self->{attrs}{prefetch}}) { - my $rel_obj = $self->{source}->_relationships->{$pre}; - my $pre_class = $self->{source}->resolve_class($rel_obj->{class}); + my $rel_obj = $self->{source}->result_class->_relationships->{$pre}; + my $pre_class = $self->{source}->result_class->resolve_class($rel_obj->{class}); my @pre_cols = $pre_class->_select_columns; my @vals = splice(@row, 0, scalar @pre_cols); my $fetched = $pre_class->_row_to_object(\@pre_cols, \@vals); - $self->{source}->throw("No accessor for prefetched $pre") + $self->{source}->result_class->throw("No accessor for prefetched $pre") unless defined $rel_obj->{attrs}{accessor}; if ($rel_obj->{attrs}{accessor} eq 'single') { foreach my $pri ($rel_obj->{class}->primary_columns) { @@ -174,7 +225,7 @@ sub _construct_object { } elsif ($rel_obj->{attrs}{accessor} eq 'filter') { $new->{_inflated_column}{$pre} = $fetched; } else { - $self->{source}->throw("Don't know how to store prefetched $pre"); + $self->{source}->result_class->throw("Don't know how to store prefetched $pre"); } } } @@ -183,36 +234,48 @@ sub _construct_object { return $new; } -=item count +=head2 count -Performs an SQL count with the same query as the resultset was built -with to find the number of elements. +Performs an SQL C with the same query as the resultset was built +with to find the number of elements. If passed arguments, does a search +on the resultset and counts the results of that. =cut - sub count { - my ($self) = @_; - my $attrs = { %{ $self->{attrs} } }; + my $self = shift; + return $self->search(@_)->count if @_ && defined $_[0]; + die "Unable to ->count with a GROUP BY" if defined $self->{attrs}{group_by}; unless ($self->{count}) { - # offset and order by are not needed to count - delete $attrs->{$_} for qw/offset order_by/; + my $attrs = { %{ $self->{attrs} }, + select => { 'count' => '*' }, + as => [ 'count' ] }; + # offset and order by are not needed to count, page, join and prefetch + # will get in the way (add themselves to from again ...) + delete $attrs->{$_} for qw/offset order_by page join prefetch/; my @cols = 'COUNT(*)'; - $self->{count} = $self->{source}->storage->select_single( - $self->{from}, \@cols, $self->{cond}, $attrs); + ($self->{count}) = $self->search(undef, $attrs)->cursor->next; } return 0 unless $self->{count}; return $self->{pager}->entries_on_this_page if ($self->{pager}); - return ( $attrs->{rows} && $attrs->{rows} < $self->{count} ) - ? $attrs->{rows} + return ( $self->{attrs}->{rows} && $self->{attrs}->{rows} < $self->{count} ) + ? $self->{attrs}->{rows} : $self->{count}; } -=item all +=head2 count_literal -Returns all elements in the resultset. Is called implictly if the search -method is used in list context. +Calls search_literal with the passed arguments, then count. + +=cut + +sub count_literal { shift->search_literal(@_)->count; } + +=head2 all + +Returns all elements in the resultset. Called implictly if the resultset +is returned in list context. =cut @@ -222,9 +285,9 @@ sub all { $self->cursor->all; } -=item reset +=head2 reset -Reset this resultset's cursor, so you can iterate through the elements again. +Resets the resultset's cursor, so you can iterate through the elements again. =cut @@ -234,9 +297,9 @@ sub reset { return $self; } -=item first +=head2 first -resets the resultset and returns the first element. +Resets the resultset and returns the first element. =cut @@ -244,7 +307,7 @@ sub first { return $_[0]->reset->next; } -=item delete +=head2 delete Deletes all elements in the resultset. @@ -258,7 +321,7 @@ sub delete { *delete_all = \&delete; # Yeah, yeah, yeah ... -=item pager +=head2 pager Returns a L object for the current resultset. Only makes sense for queries with page turned on. @@ -276,9 +339,9 @@ sub pager { return $self->{pager}; } -=item page +=head2 page($page_num) -Returns a new resultset representing a given page. +Returns a new resultset for the specified page. =cut @@ -289,49 +352,75 @@ sub page { return $self->new($self->{source}, $attrs); } -=back - =head1 Attributes -The resultset is responsible for handling the various attributes that -can be passed in with the search functions. Here's an overview of them: +The resultset takes various attributes that modify its behavior. +Here's an overview of them: + +=head2 order_by -=over 4 +Which column(s) to order the results by. This is currently passed +through directly to SQL, so you can give e.g. C for a +descending order. -=item order_by +=head2 cols (arrayref) -Which column to order the results by. +Shortcut to request a particular set of columns to be retrieved - adds +'me.' onto the start of any column without a '.' in it and sets 'select' +from that, then auto-populates 'as' from 'select' as normal -=item cols +=head2 select (arrayref) -Which cols should be retrieved on the first search. +Indicates which columns should be selected from the storage -=item join +=head2 as (arrayref) -Contains a list of relations that should be joined for this query. Can also -contain a hash referece to refer to that relation's relations. +Indicates column names for object inflation -=item from +=head2 join -This attribute can contain a arrayref of elements. each element can be another +Contains a list of relationships that should be joined for this query. Can also +contain a hash reference to refer to that relation's relations. So, if one column +in your class C foo and another C bar, you can do +C<< join => [qw/ foo bar /] >> to join both (and e.g. use them for C). +If a foo contains many margles and you want to join those too, you can do +C<< join => { foo => 'margle' } >>. If you want to fetch the columns from the +related table as well, see C below. + +=head2 prefetch + +Contains a list of relationships that should be fetched along with the main +query (when they are accessed afterwards they will have already been +"prefetched"). This is useful for when you know you will need the related +object(s), because it saves a query. Currently limited to prefetching +one relationship deep, so unlike C, prefetch must be an arrayref. + +=head2 from + +This attribute can contain a arrayref of elements. Each element can be another arrayref, to nest joins, or it can be a hash which represents the two sides of the join. -*NOTE* Use this on your own risk. This allows you to shoot your foot off! +NOTE: Use this on your own risk. This allows you to shoot your foot off! -=item page +=head2 page -Should the resultset be paged? This can also be enabled by using the -'page' option. +For a paged resultset, specifies which page to retrieve. Leave unset +for an unpaged resultset. -=item rows +=head2 rows -For paged resultsset, how many rows per page +For a paged resultset, how many rows per page -=item offset +=head2 group_by -For paged resultsset, which page to start on. +A list of columns to group by (note that 'count' doesn't work on grouped +resultsets) -=back +=head2 distinct + +Set to 1 to group by all columns + +=cut 1;