X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FDBIx%2FClass%2FResultSet.pm;h=7611945b3069f4e2ef47c1892d00d1c1f42c5563;hb=038af71cf6f6c77cb1380378d4887a7da03fe2b2;hp=88545421d2ed3d6003642033ab04ef421e87b186;hpb=dc64f1b5181dd959363bb2e3ad8363b1081792f6;p=dbsrgits%2FDBIx-Class.git diff --git a/lib/DBIx/Class/ResultSet.pm b/lib/DBIx/Class/ResultSet.pm index 8854542..7611945 100644 --- a/lib/DBIx/Class/ResultSet.pm +++ b/lib/DBIx/Class/ResultSet.pm @@ -102,6 +102,21 @@ another. }); } +=head3 Resolving conditions and attributes + +When a resultset is chained from another resultset, conditions and +attributes with the same keys need resolving. + +L, L, L, L attributes are merged +into the existing ones from the original resultset. + +The L, L attribute, and any search conditions are +merged with an SQL C to the existing condition from the original +resultset. + +All other attributes are overridden by any new ones supplied in the +search attributes. + =head2 Multiple queries Since a resultset just defines a query, you can do all sorts of @@ -264,6 +279,11 @@ always return a resultset, even in list context. sub search_rs { my $self = shift; + # Special-case handling for (undef, undef). + if ( @_ == 2 && !defined $_[1] && !defined $_[0] ) { + pop(@_); pop(@_); + } + my $attrs = {}; $attrs = pop(@_) if @_ > 1 and ref $_[$#_] eq 'HASH'; my $our_attrs = { %{$self->{attrs}} }; @@ -287,7 +307,7 @@ sub search_rs { my $new_attrs = { %{$our_attrs}, %{$attrs} }; # merge new attrs into inherited - foreach my $key (qw/join prefetch +select +as/) { + foreach my $key (qw/join prefetch +select +as bind/) { next unless exists $attrs->{$key}; $new_attrs->{$key} = $self->_merge_attr($our_attrs->{$key}, $attrs->{$key}); } @@ -373,19 +393,29 @@ Pass a literal chunk of SQL to be added to the conditional part of the resultset query. CAVEAT: C is provided for Class::DBI compatibility and should -only be used in that context. There are known problems using C -in chained queries; it can result in bind values in the wrong order. See -L and +only be used in that context. C is a convenience method. +It is equivalent to calling $schema->search(\[]), but if you want to ensure +columns are bound correctly, use C. + +Example of how to use C instead of C + + my @cds = $cd_rs->search_literal('cdid = ? AND (artist = ? OR artist = ?)', (2, 1, 2)); + my @cds = $cd_rs->search(\[ 'cdid = ? AND (artist = ? OR artist = ?)', [ 'cdid', 2 ], [ 'artist', 1 ], [ 'artist', 2 ] ]); + + +See L and L for searching techniques that do not require C. =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); + my ($self, $sql, @bind) = @_; + my $attr; + if ( @bind && ref($bind[-1]) eq 'HASH' ) { + $attr = pop @bind; + } + return $self->search(\[ $sql, map [ __DUMMY__ => $_ ], @bind ], ($attr || () )); } =head2 find @@ -830,10 +860,24 @@ You most likely want to use L with specific operators. For more information, see L. +This method is deprecated and will be removed in 0.09. Use L +instead. An example conversion is: + + ->search_like({ foo => 'bar' }); + + # Becomes + + ->search({ foo => { like => 'bar' } }); + =cut sub search_like { my $class = shift; + carp join ("\n", + 'search_like() is deprecated and will be removed in 0.09.', + 'Instead use ->search({ x => { -like => "y%" } })', + '(note the outer pair of {}s - they are important!)' + ); my $attrs = (@_ > 1 && ref $_[$#_] eq 'HASH' ? pop(@_) : {}); my $query = ref $_[0] eq 'HASH' ? { %{shift()} }: {@_}; $query->{$_} = { 'like' => $query->{$_} } for keys %$query; @@ -1077,6 +1121,11 @@ An accessor for the class to use when creating row objects. Defaults to C<< result_source->result_class >> - which in most cases is the name of the L<"table"|DBIx::Class::Manual::Glossary/"ResultSource"> class. +Note that changing the result_class will also remove any components +that were originally loaded in the source class via +L. Any overloaded methods +in the original source class will not run. + =cut sub result_class { @@ -1102,12 +1151,6 @@ 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. -Note: When using C with C, L emulates C -using C. Some databases (notably SQLite) do -not support C with multiple columns. If you are using such a -database, you should only use columns from the main table in your C -clause. - =cut sub count { @@ -1128,32 +1171,21 @@ sub count { sub _count { # Separated out so pager can get the full count my $self = shift; - my $select = { count => '*' }; - my $attrs = { %{$self->_resolved_attrs} }; - if (my $group_by = delete $attrs->{group_by}) { - delete $attrs->{having}; - my @distinct = (ref $group_by ? @$group_by : ($group_by)); - # todo: try CONCAT for multi-column pk - my @pk = $self->result_source->primary_columns; - if (@pk == 1) { - my $alias = $attrs->{alias}; - foreach my $column (@distinct) { - if ($column =~ qr/^(?:\Q${alias}.\E)?$pk[0]$/) { - @distinct = ($column); - last; - } - } - } - $select = { count => { distinct => \@distinct } }; + if (my $group_by = $attrs->{group_by}) { + delete $attrs->{order_by}; + + $attrs->{select} = $group_by; + $attrs->{from} = [ { 'mesub' => (ref $self)->new($self->result_source, $attrs)->cursor->as_query } ]; + delete $attrs->{where}; } - $attrs->{select} = $select; + $attrs->{select} = { count => '*' }; $attrs->{as} = [qw/count/]; - # offset, order by and page are not needed to count. record_filter is cdbi - delete $attrs->{$_} for qw/rows offset order_by page pager record_filter/; + # offset, order by, group by, where and page are not needed to count. record_filter is cdbi + delete $attrs->{$_} for qw/rows offset order_by group_by page pager record_filter/; my $tmp_rs = (ref $self)->new($self->result_source, $attrs); my ($count) = $tmp_rs->cursor->next; @@ -1283,49 +1315,8 @@ sub _cond_for_update_delete { # No-op. No condition, we're updating/deleting everything return $cond unless ref $full_cond; - if (ref $full_cond eq 'ARRAY') { - $cond = [ - map { - my %hash; - foreach my $key (keys %{$_}) { - $key =~ /([^.]+)$/; - $hash{$1} = $_->{$key}; - } - \%hash; - } @{$full_cond} - ]; - } - elsif (ref $full_cond eq 'HASH') { - if ((keys %{$full_cond})[0] eq '-and') { - $cond->{-and} = []; - - my @cond = @{$full_cond->{-and}}; - for (my $i = 0; $i < @cond; $i++) { - my $entry = $cond[$i]; - - my $hash; - if (ref $entry eq 'HASH') { - $hash = $self->_cond_for_update_delete($entry); - } - else { - $entry =~ /([^.]+)$/; - $hash->{$1} = $cond[++$i]; - } - - push @{$cond->{-and}}, $hash; - } - } - else { - foreach my $key (keys %{$full_cond}) { - $key =~ /([^.]+)$/; - $cond->{$1} = $full_cond->{$key}; - } - } - } - else { - $self->throw_exception( - "Can't update/delete on resultset with condition unless hash or array" - ); + foreach my $pk ($self->result_source->primary_columns) { + $cond->{$pk} = { -in => $self->get_column($pk)->as_query }; } return $cond; @@ -1353,13 +1344,8 @@ sub update { $self->throw_exception("Values for update must be a hash") unless ref $values eq 'HASH'; - carp( 'WARNING! Currently $rs->update() does not generate proper SQL' - . ' on joined resultsets, and may affect rows well outside of the' - . ' contents of $rs. Use at your own risk' ) - if ( $self->{attrs}{seen_join} ); - my $cond = $self->_cond_for_update_delete; - + return $self->result_source->storage->update( $self->result_source, $values, $cond ); @@ -1407,10 +1393,6 @@ to run. See also L. delete may not generate correct SQL for a query with joins or a resultset chained from a related resultset. In this case it will generate a warning:- - WARNING! Currently $rs->delete() does not generate proper SQL on - joined resultsets, and may delete rows well outside of the contents - of $rs. Use at your own risk - In these cases you may find that delete_all is more appropriate, or you need to respecify your query in a way that can be expressed without a join. @@ -1420,10 +1402,7 @@ sub delete { my ($self) = @_; $self->throw_exception("Delete should not be passed any arguments") if $_[1]; - carp( 'WARNING! Currently $rs->delete() does not generate proper SQL' - . ' on joined resultsets, and may delete rows well outside of the' - . ' contents of $rs. Use at your own risk' ) - if ( $self->{attrs}{seen_join} ); + my $cond = $self->_cond_for_update_delete; $self->result_source->storage->delete($self->result_source, $cond); @@ -2072,6 +2051,63 @@ sub update_or_create { return $self->create($cond); } +=head2 update_or_new + +=over 4 + +=item Arguments: \%col_values, { key => $unique_constraint }? + +=item Return Value: $rowobject + +=back + + $resultset->update_or_new({ 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, instantiate +a new result object and return it. The object will not be saved into your storage +until you call L on it. + +Takes an optional C attribute to search on a specific unique constraint. +For example: + + # In your application + my $cd = $schema->resultset('CD')->update_or_new( + { + artist => 'Massive Attack', + title => 'Mezzanine', + year => 1998, + }, + { key => 'cd_artist_title' } + ); + + if ($cd->in_storage) { + # the cd was updated + } + else { + # the cd is not yet in the database, let's insert it + $cd->insert; + } + +See also L, L and L. + +=cut + +sub update_or_new { + my $self = shift; + my $attrs = ( @_ > 1 && ref $_[$#_] eq 'HASH' ? pop(@_) : {} ); + my $cond = ref $_[0] eq 'HASH' ? shift : {@_}; + + my $row = $self->find( $cond, $attrs ); + if ( defined $row ) { + $row->update($cond); + return $row; + } + + return $self->new_result($cond); +} + =head2 get_cache =over 4 @@ -2298,12 +2334,20 @@ sub _resolved_attrs { # build columns (as long as select isn't set) into a set of as/select hashes unless ( $attrs->{select} ) { @colbits = map { - ( ref($_) eq 'HASH' ) ? $_ - : { - ( - /^\Q${alias}.\E(.+)$/ ? $1 - : $_ - ) => ( /\./ ? $_ : "${alias}.$_" ) + ( ref($_) eq 'HASH' ) + ? $_ + : { + ( + /^\Q${alias}.\E(.+)$/ + ? "$1" + : "$_" + ) + => + ( + /\./ + ? "$_" + : "${alias}.$_" + ) } } ( ref($attrs->{columns}) eq 'ARRAY' ) ? @{ delete $attrs->{columns}} : (delete $attrs->{columns} || $source->columns ); }