X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FDBIx%2FClass%2FResultSet.pm;h=a962af17a6b43dddf518f43f0e8bc0a3c4d92eaa;hb=f64e05d2c665c26e52fe7f3dc8ae035e3782cda0;hp=461a837e8d56e7225401a94d96223f0c8b255dd6;hpb=d2c26f3fdccf7aead269ab2d7c8c1d0c5b14b9a1;p=dbsrgits%2FDBIx-Class-Historic.git diff --git a/lib/DBIx/Class/ResultSet.pm b/lib/DBIx/Class/ResultSet.pm index 461a837..a962af1 100644 --- a/lib/DBIx/Class/ResultSet.pm +++ b/lib/DBIx/Class/ResultSet.pm @@ -10,6 +10,7 @@ use Data::Page; use Storable; use Scalar::Util qw/weaken/; +use DBIx::Class::ResultSetColumn; use base qw/DBIx::Class/; __PACKAGE__->load_components(qw/AccessorGroup/); __PACKAGE__->mk_group_accessors('simple' => qw/result_source result_class/); @@ -53,7 +54,13 @@ In the examples below, the following table classes are used: =head2 new -=head3 Arguments: ($source, \%$attrs) +=over 4 + +=item Arguments: $source, \%$attrs + +=item Return Value: $rs + +=back The resultset constructor. Takes a source object (usually a L) and an attribute hash (see @@ -65,6 +72,12 @@ automatically get one from e.g. a L called in scalar context: my $rs = $schema->resultset('CD')->search({ title => '100th Window' }); +IMPORTANT: If called on an object, proxies to new_result instead so + + my $cd = $schema->resultset('CD')->new({ title => 'Spoon' }); + +will return a CD object, not a ResultSet. + =cut sub new { @@ -157,11 +170,22 @@ sub new { =head2 search - my @cds = $rs->search({ year => 2001 }); # "... WHERE year = 2001" - my $new_rs = $rs->search({ year => 2005 }); +=over 4 + +=item Arguments: $cond, \%attrs? + +=item Return Value: $resultset (scalar context), @row_objs (list context) + +=back + + my @cds = $cd_rs->search({ year => 2001 }); # "... WHERE year = 2001" + my $new_rs = $cd_rs->search({ year => 2005 }); + + my $new_rs = $cd_rs->search([ { year => 2005 }, { year => 2004 } ]); + # year = 2005 OR year = 2004 If you need to pass in additional attributes but no additional condition, -call it as C. +call it as C. # "SELECT name, artistid FROM $artist_table" my @all_artists = $schema->resultset('Artist')->search(undef, { @@ -215,11 +239,19 @@ sub search { =head2 search_literal - my @obj = $rs->search_literal($literal_where_cond, @bind); - my $new_rs = $rs->search_literal($literal_where_cond, @bind); +=over 4 + +=item Arguments: $sql_fragment, @bind_values + +=item Return Value: $resultset (scalar context), @row_objs (list context) + +=back + + my @cds = $cd_rs->search_literal('year = ? AND title = ?', qw/2001 Reload/); + my $newrs = $artist_rs->search_literal('name = ?', 'Metallica'); Pass a literal chunk of SQL to be added to the conditional part of the -resultset. +resultset query. =cut @@ -232,7 +264,13 @@ sub search_literal { =head2 find -=head3 Arguments: (@colvalues) | (\%cols, \%attrs?) +=over 4 + +=item Arguments: @values | \%cols, \%attrs? + +=item Return Value: $row_object + +=back Finds a row based on its primary key or unique constraint. For example: @@ -257,49 +295,76 @@ sub find { my ($self, @vals) = @_; my $attrs = (@vals > 1 && ref $vals[$#vals] eq 'HASH' ? pop(@vals) : {}); - my @cols = $self->result_source->primary_columns; + my %unique_constraints = $self->result_source->unique_constraints; + $self->throw_exception( + "Can't find unless a primary key or unique constraint is defined" + ) unless %unique_constraints; + + my @constraint_names = keys %unique_constraints; if (exists $attrs->{key}) { - my %uniq = $self->result_source->unique_constraints; $self->throw_exception( "Unknown key $attrs->{key} on '" . $self->result_source->name . "'" - ) unless exists $uniq{$attrs->{key}}; - @cols = @{ $uniq{$attrs->{key}} }; - } - #use Data::Dumper; warn Dumper($attrs, @vals, @cols); - $self->throw_exception( - "Can't find unless a primary key or unique constraint is defined" - ) unless @cols; - - my $query; - if (ref $vals[0] eq 'HASH') { - $query = { %{$vals[0]} }; - } elsif (@cols == @vals) { - $query = {}; - @{$query}{@cols} = @vals; - } else { - $query = {@vals}; + ) unless exists $unique_constraints{$attrs->{key}}; + + @constraint_names = ($attrs->{key}); } - foreach my $key (grep { ! m/\./ } keys %$query) { - $query->{"$self->{attrs}{alias}.$key"} = delete $query->{$key}; + + my @unique_hashes; + foreach my $name (@constraint_names) { + my @unique_cols = @{ $unique_constraints{$name} }; + my %unique_hash; + if (ref $vals[0] eq 'HASH') { + %unique_hash = + map { $_ => $vals[0]->{$_} } + grep { exists $vals[0]->{$_} } + @unique_cols; + } + elsif (@unique_cols == @vals) { + # Assume the argument order corresponds to the constraint definition + @unique_hash{@unique_cols} = @vals; + } + elsif (@vals % 2 == 0) { + # Fix for CDBI calling with a hash + %unique_hash = @vals; + } + + foreach my $key (grep { ! m/\./ } keys %unique_hash) { + $unique_hash{"$self->{attrs}{alias}.$key"} = delete $unique_hash{$key}; + } + + #use Data::Dumper; warn Dumper \@vals, \@unique_cols, \%unique_hash; + push @unique_hashes, \%unique_hash if %unique_hash; } - #warn Dumper($query); - + + # Handle cases where the ResultSet already defines the query + my $query = @unique_hashes ? \@unique_hashes : undef; + if (keys %$attrs) { - my $rs = $self->search($query,$attrs); - return keys %{$rs->{collapse}} ? $rs->next : $rs->single; + my $rs = $self->search($query,$attrs); + return keys %{$rs->{collapse}} ? $rs->next : $rs->single; } else { - return keys %{$self->{collapse}} ? - $self->search($query)->next : - $self->single($query); + return keys %{$self->{collapse}} + ? $self->search($query)->next + : $self->single($query); } } =head2 search_related - $rs->search_related('relname', $cond?, $attrs?); +=over 4 -Search the specified relationship. Optionally specify a condition for matching -records. +=item Arguments: $cond, \%attrs? + +=item Return Value: $new_resultset + +=back + + $new_rs = $cd_rs->search_related('artist', { + name => 'Emo-R-Us', + }); + +Searches the specified relationship, optionally specifying a condition and +attributes for matching records. See L for more information. =cut @@ -309,7 +374,16 @@ sub search_related { =head2 cursor -Returns a storage-driven cursor to the given resultset. +=over 4 + +=item Arguments: none + +=item Return Value: $cursor + +=back + +Returns a storage-driven cursor to the given resultset. See +L for more information. =cut @@ -323,7 +397,18 @@ sub cursor { =head2 single -Inflates the first result without creating a cursor +=over 4 + +=item Arguments: $cond? + +=item Return Value: $row_object? + +=back + + my $cd = $schema->resultset('CD')->single({ year => 2001 }); + +Inflates the first result without creating a cursor if the resultset has +any records in it; if not returns nothing. Used by find() as an optimisation. =cut @@ -333,7 +418,7 @@ sub single { if ($where) { if (defined $attrs->{where}) { $attrs->{where} = { - '-and' => + '-and' => [ map { ref $_ eq 'ARRAY' ? [ -or => $_ ] : $_ } $where, delete $attrs->{where} ] }; @@ -347,11 +432,44 @@ sub single { return (@data ? $self->_construct_object(@data) : ()); } +=head2 get_column + +=over 4 + +=item Arguments: $cond? + +=item Return Value: $resultsetcolumn + +=back + + my $max_length = $rs->get_column('length')->max; + +Returns a ResultSetColumn instance for $column based on $self + +=cut + +sub get_column { + my ($self, $column) = @_; + + my $new = DBIx::Class::ResultSetColumn->new($self, $column); + return $new; +} =head2 search_like -Perform a search, but use C instead of equality as the condition. Note -that this is simply a convenience method; you most likely want to use +=over 4 + +=item Arguments: $cond, \%attrs? + +=item Return Value: $resultset (scalar context), @row_objs (list context) + +=back + + # WHERE title LIKE '%blue%' + $cd_rs = $rs->search_like({ title => '%blue%'}); + +Performs a search, but uses C instead of C<=> as the condition. Note +that this is simply a convenience method. You most likely want to use L with specific operators. For more information, see L. @@ -368,24 +486,43 @@ sub search_like { =head2 slice -=head3 Arguments: ($first, $last) +=over 4 -Returns a subset of elements from the resultset. +=item Arguments: $first, $last + +=item Return Value: $resultset (scalar context), @row_objs (list context) + +=back + +Returns a resultset or object list representing a subset of elements from the +resultset slice is called on. Indexes are from 0, i.e., to get the first +three records, call: + + my ($one, $two, $three) = $rs->slice(0, 2); =cut sub slice { my ($self, $min, $max) = @_; - my $attrs = { %{ $self->{attrs} || {} } }; - $attrs->{offset} ||= 0; + my $attrs = {}; # = { %{ $self->{attrs} || {} } }; + $attrs->{offset} = $self->{attrs}{offset} || 0; $attrs->{offset} += $min; $attrs->{rows} = ($max ? ($max - $min + 1) : 1); - my $slice = (ref $self)->new($self->result_source, $attrs); - return (wantarray ? $slice->all : $slice); + return $self->search(undef(), $attrs); + #my $slice = (ref $self)->new($self->result_source, $attrs); + #return (wantarray ? $slice->all : $slice); } =head2 next +=over 4 + +=item Arguments: none + +=item Return Value: $result? + +=back + Returns the next element in the resultset (C is there is none). Can be used to efficiently iterate over records in the resultset: @@ -395,6 +532,10 @@ Can be used to efficiently iterate over records in the resultset: print $cd->title; } +Note that you need to store the resultset object, and call C on it. +Calling C<< resultset('Table')->next >> repeatedly will always return the +first record from the resultset. + =cut sub next { @@ -408,8 +549,8 @@ sub next { return ($self->all)[0]; } my @row = (exists $self->{stashed_row} ? - @{delete $self->{stashed_row}} : - $self->cursor->next + @{delete $self->{stashed_row}} : + $self->cursor->next ); # warn Dumper(\@row); use Data::Dumper; return unless (@row); @@ -466,7 +607,7 @@ sub _collapse_result { my @collapse; if (defined $prefix) { @collapse = map { - m/^\Q${prefix}.\E(.+)$/ ? ($1) : () + m/^\Q${prefix}.\E(.+)$/ ? ($1) : () } keys %{$self->{collapse}} } else { @collapse = keys %{$self->{collapse}}; @@ -485,7 +626,7 @@ sub _collapse_result { my (@final, @raw); while ( !(grep { !defined($tree->[0]->{$_}) || - $co_check{$_} ne $tree->[0]->{$_} + $co_check{$_} ne $tree->[0]->{$_} } @co_key) ) { push(@final, $tree); last unless (@raw = $self->cursor->next); @@ -501,13 +642,30 @@ sub _collapse_result { =head2 result_source -Returns a reference to the result source for this recordset. +=over 4 + +=item Arguments: $result_source? + +=item Return Value: $result_source + +=back + +An accessor for the primary ResultSource object from which this ResultSet +is derived. =cut =head2 count +=over 4 + +=item Arguments: $cond, \%attrs?? + +=item Return Value: $count + +=back + 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. @@ -549,7 +707,7 @@ sub _count { # Separated out so pager can get the full count @distinct = ($column); last; } - } + } } $select = { count => { distinct => \@distinct } }; @@ -568,7 +726,16 @@ sub _count { # Separated out so pager can get the full count =head2 count_literal -Calls L with the passed arguments, then L. +=over 4 + +=item Arguments: $sql_fragment, @bind_values + +=item Return Value: $count + +=back + +Counts the results in a literal query. Equivalent to calling L +with the passed arguments, then L. =cut @@ -576,7 +743,15 @@ sub count_literal { shift->search_literal(@_)->count; } =head2 all -Returns all elements in the resultset. Called implictly if the resultset +=over 4 + +=item Arguments: none + +=item Return Value: @objects + +=back + +Returns all elements in the resultset. Called implicitly if the resultset is returned in list context. =cut @@ -610,6 +785,14 @@ sub all { =head2 reset +=over 4 + +=item Arguments: none + +=item Return Value: $self + +=back + Resets the resultset's cursor, so you can iterate through the elements again. =cut @@ -623,7 +806,16 @@ sub reset { =head2 first -Resets the resultset and returns the first element. +=over 4 + +=item Arguments: none + +=item Return Value: $object? + +=back + +Resets the resultset and returns an object for the first result (if the +resultset returns anything). =cut @@ -631,11 +823,84 @@ sub first { return $_[0]->reset->next; } +# _cond_for_update_delete +# +# update/delete require the condition to be modified to handle +# the differing SQL syntax available. This transforms the $self->{cond} +# appropriately, returning the new condition. + +sub _cond_for_update_delete { + my ($self) = @_; + my $cond = {}; + + if (!ref($self->{cond})) { + # No-op. No condition, we're updating/deleting everything + } + elsif (ref $self->{cond} eq 'ARRAY') { + $cond = [ + map { + my %hash; + foreach my $key (keys %{$_}) { + $key =~ /([^.]+)$/; + $hash{$1} = $_->{$key}; + } + \%hash; + } @{$self->{cond}} + ]; + } + elsif (ref $self->{cond} eq 'HASH') { + if ((keys %{$self->{cond}})[0] eq '-and') { + $cond->{-and} = []; + + my @cond = @{$self->{cond}{-and}}; + for (my $i = 0; $i < @cond - 1; $i++) { + my $entry = $cond[$i]; + + my %hash; + if (ref $entry eq 'HASH') { + foreach my $key (keys %{$entry}) { + $key =~ /([^.]+)$/; + $hash{$1} = $entry->{$key}; + } + } + else { + $entry =~ /([^.]+)$/; + $hash{$entry} = $cond[++$i]; + } + + push @{$cond->{-and}}, \%hash; + } + } + else { + foreach my $key (keys %{$self->{cond}}) { + $key =~ /([^.]+)$/; + $cond->{$1} = $self->{cond}{$key}; + } + } + } + else { + $self->throw_exception( + "Can't update/delete on resultset with condition unless hash or array" + ); + } + + return $cond; +} + + =head2 update -=head3 Arguments: (\%values) +=over 4 + +=item Arguments: \%values + +=item Return Value: $storage_rv + +=back -Sets the specified columns in the resultset to the supplied values. +Sets the specified columns in the resultset to the supplied values in a +single query. Return value will be true if the update succeeded or false +if no records were updated; exact type of success value is storage-dependent. =cut @@ -643,16 +908,26 @@ sub update { my ($self, $values) = @_; $self->throw_exception("Values for update must be a hash") unless ref $values eq 'HASH'; + + my $cond = $self->_cond_for_update_delete; + return $self->result_source->storage->update( - $self->result_source->from, $values, $self->{cond}); + $self->result_source->from, $values, $cond + ); } =head2 update_all -=head3 Arguments: (\%values) +=over 4 + +=item Arguments: \%values -Fetches all objects and updates them one at a time. Note that C -will run cascade triggers while L will not. +=item Return Value: 1 + +=back + +Fetches all objects and updates them one at a time. Note that C +will run DBIC cascade triggers, while L will not. =cut @@ -668,56 +943,42 @@ sub update_all { =head2 delete -Deletes the contents of the resultset from its result source. +=over 4 -=cut +=item Arguments: none -sub delete { - my ($self) = @_; - my $del = {}; +=item Return Value: 1 - if (!ref($self->{cond})) { +=back - # No-op. No condition, we're deleting everything +Deletes the contents of the resultset from its result source. Note that this +will not run DBIC cascade triggers. See L if you need triggers +to run. - } elsif (ref $self->{cond} eq 'ARRAY') { +=cut - $del = [ map { my %hash; - foreach my $key (keys %{$_}) { - $key =~ /([^.]+)$/; - $hash{$1} = $_->{$key}; - }; \%hash; } @{$self->{cond}} ]; +sub delete { + my ($self) = @_; + my $del = {}; - } elsif (ref $self->{cond} eq 'HASH') { + my $cond = $self->_cond_for_update_delete; - if ((keys %{$self->{cond}})[0] eq '-and') { + $self->result_source->storage->delete($self->result_source->from, $cond); + return 1; +} - $del->{-and} = [ map { my %hash; - foreach my $key (keys %{$_}) { - $key =~ /([^.]+)$/; - $hash{$1} = $_->{$key}; - }; \%hash; } @{$self->{cond}{-and}} ]; +=head2 delete_all - } else { +=over 4 - foreach my $key (keys %{$self->{cond}}) { - $key =~ /([^.]+)$/; - $del->{$1} = $self->{cond}{$key}; - } - } - } else { - $self->throw_exception( - "Can't delete on resultset with condition unless hash or array"); - } +=item Arguments: none - $self->result_source->storage->delete($self->result_source->from, $del); - return 1; -} +=item Return Value: 1 -=head2 delete_all +=back -Fetches all objects and deletes them one at a time. Note that C -will run cascade triggers while L will not. +Fetches all objects and deletes them one at a time. Note that C +will run DBIC cascade triggers, while L will not. =cut @@ -729,7 +990,15 @@ sub delete_all { =head2 pager -Returns a L object for the current resultset. Only makes +=over 4 + +=item Arguments: none + +=item Return Value: $pager + +=back + +Return Value a L object for the current resultset. Only makes sense for queries with a C attribute. =cut @@ -746,9 +1015,17 @@ sub pager { =head2 page -=head3 Arguments: ($page_num) +=over 4 + +=item Arguments: $page_number + +=item Return Value: $rs -Returns a new resultset for the specified page. +=back + +Returns a resultset for the $page_number page of the resultset on which page +is called, where each page contains a number of rows equal to the 'rows' +attribute set on the resultset (10 by default). =cut @@ -761,9 +1038,15 @@ sub page { =head2 new_result -=head3 Arguments: (\%vals) +=over 4 + +=item Arguments: \%vals + +=item Return Value: $object + +=back -Creates a result in the resultset's result class. +Creates an object in the resultset's result class and returns it. =cut @@ -786,9 +1069,15 @@ sub new_result { =head2 create -=head3 Arguments: (\%vals) +=over 4 -Inserts a record into the resultset and returns the object. +=item Arguments: \%vals + +=item Return Value: $object + +=back + +Inserts a record into the resultset and returns the object representing it. Effectively a shortcut for C<< ->new_result(\%vals)->insert >>. @@ -803,7 +1092,13 @@ sub create { =head2 find_or_create -=head3 Arguments: (\%vals, \%attrs?) +=over 4 + +=item Arguments: \%vals, \%attrs? + +=item Return Value: $object + +=back $class->find_or_create({ key => $val, ... }); @@ -842,11 +1137,19 @@ sub find_or_create { =head2 update_or_create - $class->update_or_create({ key => $val, ... }); +=over 4 + +=item Arguments: \%col_values, { key => $unique_constraint }? -First, search for an existing row matching one of the unique constraints -(including the primary key) on the source of this resultset. If a row is -found, update it with the other given column values. Otherwise, create a new +=item Return Value: $object + +=back + + $class->update_or_create({ col => $val, ... }); + +First, searches for an existing row matching one of the unique constraints +(including the primary key) on the source of this resultset. If a row is +found, updates it with the other given column values. Otherwise, creates a new row. Takes an optional C attribute to search on a specific unique constraint. @@ -865,7 +1168,7 @@ For example: If no C is specified, it searches on all unique constraints defined on the source, including the primary key. -If the C is specified as C, search only on the primary key. +If the C is specified as C, it searches only on the primary key. See also L and L. @@ -876,30 +1179,11 @@ sub update_or_create { my $attrs = (@_ > 1 && ref $_[$#_] eq 'HASH' ? pop(@_) : {}); my $hash = ref $_[0] eq 'HASH' ? shift : {@_}; - my %unique_constraints = $self->result_source->unique_constraints; - my @constraint_names = (exists $attrs->{key} - ? ($attrs->{key}) - : keys %unique_constraints); - - my @unique_hashes; - foreach my $name (@constraint_names) { - my @unique_cols = @{ $unique_constraints{$name} }; - my %unique_hash = - map { $_ => $hash->{$_} } - grep { exists $hash->{$_} } - @unique_cols; - - push @unique_hashes, \%unique_hash - if (scalar keys %unique_hash == scalar @unique_cols); - } - - if (@unique_hashes) { - my $row = $self->single(\@unique_hashes); - if (defined $row) { - $row->set_columns($hash); - $row->update; - return $row; - } + my $row = $self->find($hash, $attrs); + if (defined $row) { + $row->set_columns($hash); + $row->update; + return $row; } return $self->create($hash); @@ -907,7 +1191,15 @@ sub update_or_create { =head2 get_cache -Gets the contents of the cache for the resultset. +=over 4 + +=item Arguments: none + +=item Return Value: \@cache_objects? + +=back + +Gets the contents of the cache for the resultset, if the cache is set. =cut @@ -917,8 +1209,18 @@ sub get_cache { =head2 set_cache +=over 4 + +=item Arguments: \@cache_objects + +=item Return Value: \@cache_objects + +=back + Sets the contents of the cache for the resultset. Expects an arrayref -of objects of the same class as those produced by the resultset. +of objects of the same class as those produced by the resultset. Note that +if the cache is set the resultset will return the cached objects rather +than re-querying the database even if the cache attr is not set. =cut @@ -937,6 +1239,14 @@ sub set_cache { =head2 clear_cache +=over 4 + +=item Arguments: none + +=item Return Value: [] + +=back + Clears the cache for the resultset. =cut @@ -947,6 +1257,14 @@ sub clear_cache { =head2 related_resultset +=over 4 + +=item Arguments: $relationship_name + +=item Return Value: $resultset + +=back + Returns a related resultset for the supplied relationship name. $artist_rs = $schema->resultset('CD')->related_resultset('Artist'); @@ -954,7 +1272,7 @@ Returns a related resultset for the supplied relationship name. =cut sub related_resultset { - my ( $self, $rel, @rest ) = @_; + my ( $self, $rel ) = @_; $self->{related_resultsets} ||= {}; return $self->{related_resultsets}{$rel} ||= do { #warn "fetching related resultset for rel '$rel'"; @@ -976,13 +1294,13 @@ sub related_resultset { alias => $alias, select => undef, as => undef } - )->search(@rest); + ); }; } =head2 throw_exception -See Schema's throw_exception +See L for details. =cut @@ -991,6 +1309,8 @@ sub throw_exception { $self->result_source->schema->throw_exception(@_); } +# XXX: FIXME: Attributes docs need clearing up + =head1 ATTRIBUTES The resultset takes various attributes that modify its behavior. Here's an @@ -998,13 +1318,23 @@ overview of them: =head2 order_by +=over 4 + +=item Value: ($order_by | \@order_by) + +=back + 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 on the column `year'. =head2 columns -=head3 Arguments: (arrayref) +=over 4 + +=item Value: \@columns + +=back Shortcut to request a particular set of columns to be retrieved. Adds C onto the start of any column without a C<.> in it and sets C, usually when C