X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FDBIx%2FClass%2FResultSet.pm;h=f998f92b973bc05ea98ba8b222340c2943e9c8a3;hb=482da5c13c9acbf3c20ab998199c039a24630bc3;hp=86b0b7efb8cc24e81a93a9f17605a20f3931b44d;hpb=31cf19abf3e6303bfac06dd5e549417ea63f3836;p=dbsrgits%2FDBIx-Class.git diff --git a/lib/DBIx/Class/ResultSet.pm b/lib/DBIx/Class/ResultSet.pm index 86b0b7e..f998f92 100644 --- a/lib/DBIx/Class/ResultSet.pm +++ b/lib/DBIx/Class/ResultSet.pm @@ -160,14 +160,29 @@ always return a resultset, even in list context. sub search_rs { my $self = shift; - my $our_attrs = { %{$self->{attrs}} }; - my $having = delete $our_attrs->{having}; my $attrs = {}; $attrs = pop(@_) if @_ > 1 and ref $_[$#_] eq 'HASH'; - + my $our_attrs = ($attrs->{_parent_attrs}) ? { %{$attrs->{_parent_attrs}} } : { %{$self->{attrs}} }; + my $having = delete $our_attrs->{having}; + + # XXX this is getting messy + if ($attrs->{_live_join_stack}) { + my $live_join = $attrs->{_live_join_stack}; + foreach (reverse @{$live_join}) { + $attrs->{_live_join_h} = (defined $attrs->{_live_join_h}) ? { $_ => $attrs->{_live_join_h} } : $_; + } + } + # merge new attrs into old foreach my $key (qw/join prefetch/) { next unless (exists $attrs->{$key}); + if ($attrs->{_live_join_stack} || $our_attrs->{_live_join_stack}) { + my $live_join = $attrs->{_live_join_stack} || $our_attrs->{_live_join_stack}; + foreach (reverse @{$live_join}) { + $attrs->{$key} = { $_ => $attrs->{$key} }; + } + } + if (exists $our_attrs->{$key}) { $our_attrs->{$key} = $self->_merge_attr($our_attrs->{$key}, $attrs->{$key}); } else { @@ -176,13 +191,13 @@ sub search_rs { delete $attrs->{$key}; } + $our_attrs->{join} = $self->_merge_attr($our_attrs->{join}, $attrs->{_live_join_h}, 1) if ($attrs->{_live_join_h}); + if (exists $our_attrs->{prefetch}) { $our_attrs->{join} = $self->_merge_attr($our_attrs->{join}, $our_attrs->{prefetch}, 1); } my $new_attrs = { %{$our_attrs}, %{$attrs} }; - - # merge new where and having into old my $where = (@_ ? ((@_ == 1 || ref $_[0] eq "HASH") ? shift @@ -263,7 +278,7 @@ a row by its primary key: You can also find a row by a specific unique constraint using the C attribute. For example: - my $cd = $schema->resultset('CD')->find('Massive Attack', 'Mezzanine', { key => 'artist_title' }); + my $cd = $schema->resultset('CD')->find('Massive Attack', 'Mezzanine', { key => 'cd_artist_title' }); Additionally, you can specify the columns explicitly by name: @@ -272,7 +287,7 @@ Additionally, you can specify the columns explicitly by name: artist => 'Massive Attack', title => 'Mezzanine', }, - { key => 'artist_title' } + { key => 'cd_artist_title' } ); If the C is specified as C, it searches only on the primary key. @@ -309,26 +324,15 @@ sub find { } else { # Compatibility: Allow e.g. find(id => $value) - carp "find by key => value deprecated; please use a hashref instead"; + carp "Find by key => value deprecated; please use a hashref instead"; $input_query = {@_}; } my @unique_queries = $self->_unique_queries($input_query, $attrs); -# use Data::Dumper; warn Dumper $self->result_source->name, $input_query, \@unique_queries; - # Verify the query - my $query = \@unique_queries; - if (scalar @unique_queries == 0) { - if (exists $attrs->{key}) { - $self->throw_exception("required values for the $attrs->{key} key not provided"); - } - else { - # Compatibility: Allow broken find usage for now - carp "find requires values for the primary key or a unique constraint" - . "; please declare your unique constraints or use search instead"; - $query = $input_query; - } - } + # Handle cases where the ResultSet defines the query, or where the user is + # abusing find + my $query = @unique_queries ? \@unique_queries : $input_query; # Run the query if (keys %$attrs) { @@ -360,11 +364,12 @@ sub _unique_queries { my @unique_cols = $self->result_source->unique_constraint_columns($name); my $unique_query = $self->_build_unique_query($query, \@unique_cols); - next unless scalar keys %$unique_query == scalar @unique_cols; + next unless scalar keys %$unique_query; # Add the ResultSet's alias foreach my $key (grep { ! m/\./ } keys %$unique_query) { - $unique_query->{"$self->{attrs}{alias}.$key"} = delete $unique_query->{$key}; + my $alias = ($self->{attrs}->{_live_join}) ? $self->{attrs}->{_live_join} : $self->{attrs}->{alias}; + $unique_query->{"$alias.$key"} = delete $unique_query->{$key}; } push @unique_queries, $unique_query; @@ -473,12 +478,84 @@ sub single { } } + unless ($self->_is_unique_query($attrs->{where})) { + carp "Query not guaranteed to return a single row" + . "; please declare your unique constraints or use search instead"; + } + my @data = $self->result_source->storage->select_single( $attrs->{from}, $attrs->{select}, $attrs->{where},$attrs); return (@data ? $self->_construct_object(@data) : ()); } +# _is_unique_query +# +# Try to determine if the specified query is guaranteed to be unique, based on +# the declared unique constraints. + +sub _is_unique_query { + my ($self, $query) = @_; + + my $collapsed = $self->_collapse_query($query); + + my $alias = ($self->{attrs}->{_live_join}) ? $self->{attrs}->{_live_join} : $self->{attrs}->{alias}; + foreach my $name ($self->result_source->unique_constraint_names) { + my @unique_cols = map { "$alias.$_" } + $self->result_source->unique_constraint_columns($name); + + # Count the values for each unique column + my %seen = map { $_ => 0 } @unique_cols; + + foreach my $key (keys %$collapsed) { + my $aliased = $key; + $aliased = "$alias.$key" unless $key =~ /\./; + + next unless exists $seen{$aliased}; # Additional constraints are okay + $seen{$aliased} = scalar @{ $collapsed->{$key} }; + } + + # If we get 0 or more than 1 value for a column, it's not necessarily unique + return 1 unless grep { $_ != 1 } values %seen; + } + + return 0; +} + +# _collapse_query +# +# Recursively collapse the query, accumulating values for each column. + +sub _collapse_query { + my ($self, $query, $collapsed) = @_; + + $collapsed ||= {}; + + if (ref $query eq 'ARRAY') { + foreach my $subquery (@$query) { + next unless ref $subquery; # -or +# warn "ARRAY: " . Dumper $subquery; + $collapsed = $self->_collapse_query($subquery, $collapsed); + } + } + elsif (ref $query eq 'HASH') { + if (keys %$query and (keys %$query)[0] eq '-and') { + foreach my $subquery (@{$query->{-and}}) { +# warn "HASH: " . Dumper $subquery; + $collapsed = $self->_collapse_query($subquery, $collapsed); + } + } + else { +# warn "LEAF: " . Dumper $query; + foreach my $key (keys %$query) { + push @{$collapsed->{$key}}, $query->{$key}; + } + } + } + + return $collapsed; +} + =head2 get_column =over 4 @@ -608,7 +685,7 @@ sub _resolve { return if(exists $self->{_attrs}); #return if _resolve has already been called - my $attrs = $self->{attrs}; + my $attrs = $self->{attrs}; my $source = ($self->{_parent_rs}) ? $self->{_parent_rs} : $self->{result_source}; # XXX - lose storable dclone @@ -638,32 +715,32 @@ sub _resolve { $attrs->{seen_join} ||= {}; my %seen; if (my $join = delete $attrs->{join}) { - foreach my $j (ref $join eq 'ARRAY' ? @$join : ($join)) { - if (ref $j eq 'HASH') { - $seen{$_} = 1 foreach keys %$j; - } else { - $seen{$j} = 1; - } + foreach my $j (ref $join eq 'ARRAY' ? @$join : ($join)) { + if (ref $j eq 'HASH') { + $seen{$_} = 1 foreach keys %$j; + } else { + $seen{$j} = 1; } - - push(@{$attrs->{from}}, $source->resolve_join($join, $attrs->{alias}, $attrs->{seen_join})); + } + + push(@{$attrs->{from}}, $source->resolve_join($join, $attrs->{alias}, $attrs->{seen_join})); } $attrs->{group_by} ||= $attrs->{select} if delete $attrs->{distinct}; $attrs->{order_by} = [ $attrs->{order_by} ] if $attrs->{order_by} and !ref($attrs->{order_by}); $attrs->{order_by} ||= []; - if(my $seladds = delete($attrs->{'+select'})) { - my @seladds = (ref($seladds) eq 'ARRAY' ? @$seladds : ($seladds)); - $attrs->{select} = [ - @{ $attrs->{select} }, - map { (m/\./ || ref($_)) ? $_ : "${alias}.$_" } $seladds - ]; - } - if(my $asadds = delete($attrs->{'+as'})) { - my @asadds = (ref($asadds) eq 'ARRAY' ? @$asadds : ($asadds)); - $attrs->{as} = [ @{ $attrs->{as} }, @asadds ]; - } + if(my $seladds = delete($attrs->{'+select'})) { + my @seladds = (ref($seladds) eq 'ARRAY' ? @$seladds : ($seladds)); + $attrs->{select} = [ + @{ $attrs->{select} }, + map { (m/\./ || ref($_)) ? $_ : "${alias}.$_" } $seladds + ]; + } + if(my $asadds = delete($attrs->{'+as'})) { + my @asadds = (ref($asadds) eq 'ARRAY' ? @$asadds : ($asadds)); + $attrs->{as} = [ @{ $attrs->{as} }, @asadds ]; + } my $collapse = $attrs->{collapse} || {}; if (my $prefetch = delete $attrs->{prefetch}) { @@ -694,58 +771,64 @@ sub _merge_attr { return $b unless $a; if (ref $b eq 'HASH' && ref $a eq 'HASH') { - foreach my $key (keys %{$b}) { - if (exists $a->{$key}) { - $a->{$key} = $self->_merge_attr($a->{$key}, $b->{$key}, $is_prefetch); - } else { - $a->{$key} = delete $b->{$key}; - } - } - return $a; + foreach my $key (keys %{$b}) { + if (exists $a->{$key}) { + $a->{$key} = $self->_merge_attr($a->{$key}, $b->{$key}, $is_prefetch); + } else { + $a->{$key} = delete $b->{$key}; + } + } + return $a; } else { - $a = [$a] unless (ref $a eq 'ARRAY'); - $b = [$b] unless (ref $b eq 'ARRAY'); - - my $hash = {}; - my $array = []; - foreach ($a, $b) { - foreach my $element (@{$_}) { - if (ref $element eq 'HASH') { - $hash = $self->_merge_attr($hash, $element, $is_prefetch); - } elsif (ref $element eq 'ARRAY') { - $array = [@{$array}, @{$element}]; - } else { - if (($b == $_) && $is_prefetch) { - $self->_merge_array($array, $element, $is_prefetch); - } else { - push(@{$array}, $element); - } - } - } - } + $a = [$a] unless (ref $a eq 'ARRAY'); + $b = [$b] unless (ref $b eq 'ARRAY'); + + my $hash = {}; + my $array = []; + foreach ($a, $b) { + foreach my $element (@{$_}) { + if (ref $element eq 'HASH') { + $hash = $self->_merge_attr($hash, $element, $is_prefetch); + } elsif (ref $element eq 'ARRAY') { + $array = [@{$array}, @{$element}]; + } else { + if (($b == $_) && $is_prefetch) { + $self->_merge_array($array, $element, $is_prefetch); + } else { + push(@{$array}, $element); + } + } + } + } - if ((keys %{$hash}) && (scalar(@{$array} > 0))) { - return [$hash, @{$array}]; - } else { - return (keys %{$hash}) ? $hash : $array; + my $final_array = []; + foreach my $element (@{$array}) { + push(@{$final_array}, $element) unless (exists $hash->{$element}); } + $array = $final_array; + + if ((keys %{$hash}) && (scalar(@{$array} > 0))) { + return [$hash, @{$array}]; + } else { + return (keys %{$hash}) ? $hash : $array; + } } } sub _merge_array { - my ($self, $a, $b) = @_; - - $b = [$b] unless (ref $b eq 'ARRAY'); - # add elements from @{$b} to @{$a} which aren't already in @{$a} - foreach my $b_element (@{$b}) { - push(@{$a}, $b_element) unless grep {$b_element eq $_} @{$a}; - } + my ($self, $a, $b) = @_; + + $b = [$b] unless (ref $b eq 'ARRAY'); + # add elements from @{$b} to @{$a} which aren't already in @{$a} + foreach my $b_element (@{$b}) { + push(@{$a}, $b_element) unless grep {$b_element eq $_} @{$a}; + } } sub _construct_object { my ($self, @row) = @_; my @as = @{ $self->{_attrs}{as} }; - + my $info = $self->_collapse_result(\@as, \@row); my $new = $self->result_class->inflate_result($self->result_source, @$info); $new = $self->{_attrs}{record_filter}->($new) @@ -865,7 +948,6 @@ sub count { my $self = shift; return $self->search(@_)->count if @_ and defined $_[0]; return scalar @{ $self->get_cache } if $self->get_cache; - my $count = $self->_count; return 0 unless $count; @@ -903,7 +985,10 @@ sub _count { # Separated out so pager can get the full 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/; - my ($count) = (ref $self)->new($self->result_source, $attrs)->cursor->next; + my $tmp_rs = (ref $self)->new($self->result_source, $attrs); + $tmp_rs->{_parent_rs} = $self->{_parent_rs} if ($self->{_parent_rs}); #XXX - hack to pass through parent of related resultsets + + my ($count) = $tmp_rs->cursor->next; return $count; } @@ -1333,7 +1418,7 @@ constraint. For example: artist => 'Massive Attack', title => 'Mezzanine', }, - { key => 'artist_title' } + { key => 'cd_artist_title' } ); See also L and L. For information on how to declare @@ -1376,7 +1461,7 @@ For example: title => 'Mezzanine', year => 1998, }, - { key => 'artist_title' } + { key => 'cd_artist_title' } ); If no C is specified, it searches on all unique constraints defined on the @@ -1481,28 +1566,31 @@ Returns a related resultset for the supplied relationship name. sub related_resultset { my ( $self, $rel ) = @_; - + $self->{related_resultsets} ||= {}; return $self->{related_resultsets}{$rel} ||= do { - #warn "fetching related resultset for rel '$rel' " . $self->result_source->{name}; - my $rel_obj = $self->result_source->relationship_info($rel); - $self->throw_exception( + #warn "fetching related resultset for rel '$rel' " . $self->result_source->{name}; + my $rel_obj = $self->result_source->relationship_info($rel); + $self->throw_exception( "search_related: result source '" . $self->result_source->name . "' has no such relationship ${rel}") unless $rel_obj; #die Dumper $self->{attrs}; - my $rs = $self->result_source->schema->resultset($rel_obj->{class} + my $live_join_stack = $self->{attrs}->{_live_join_stack} || []; + push(@{$live_join_stack}, $rel); + + my $rs = $self->result_source->schema->resultset($rel_obj->{class} )->search( undef, - { %{$self->{attrs}}, - select => undef, - as => undef, - join => $rel, - _live_join => $rel } - ); - - # keep reference of the original resultset - $rs->{_parent_rs} = $self->result_source; - return $rs; + { select => undef, + as => undef, + _live_join => $rel, #the most recent + _live_join_stack => $live_join_stack, #the trail of rels + _parent_attrs => $self->{attrs}} + ); + + # keep reference of the original resultset + $rs->{_parent_rs} = ($self->{_parent_rs}) ? $self->{_parent_rs} : $self->result_source; + return $rs; }; }