X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FDBIx%2FClass%2FResultSet.pm;h=8e44f82ef4c29f8e42684333e1085cca799233c2;hb=8dc9c09f877e9719bef9470a5d376f0d5351786f;hp=b2fa44ef657082af49bb0f46d924bddf20543d56;hpb=2758a6519f84a6d918dac45b4d470efd37063be8;p=dbsrgits%2FDBIx-Class-Historic.git diff --git a/lib/DBIx/Class/ResultSet.pm b/lib/DBIx/Class/ResultSet.pm index b2fa44e..8e44f82 100644 --- a/lib/DBIx/Class/ResultSet.pm +++ b/lib/DBIx/Class/ResultSet.pm @@ -275,11 +275,11 @@ Additionally, you can specify the columns explicitly by name: { key => 'artist_title' } ); -If no C is specified and you explicitly name columns, it searches on all -unique constraints defined on the source, including the primary key. - If the C is specified as C, it searches only on the primary key. +If no C is specified, it searches on all unique constraints defined on the +source, including the primary key. + See also L and L. For information on how to declare unique constraints, see L. @@ -290,74 +290,81 @@ sub find { my $self = shift; my $attrs = (@_ > 1 && ref $_[$#_] eq 'HASH' ? pop(@_) : {}); - # Parse out a hash from input + # Default to the primary key, but allow a specific key my @cols = exists $attrs->{key} ? $self->result_source->unique_constraint_columns($attrs->{key}) : $self->result_source->primary_columns; + $self->throw_exception( + "Can't find unless a primary key or unique constraint is defined" + ) unless @cols; - my $hash; + # Parse out a hashref from input + my $input_query; if (ref $_[0] eq 'HASH') { - $hash = { %{$_[0]} }; + $input_query = { %{$_[0]} }; } elsif (@_ == @cols) { - $hash = {}; - @{$hash}{@cols} = @_; + $input_query = {}; + @{$input_query}{@cols} = @_; } - elsif (@_) { + else { # Compatibility: Allow e.g. find(id => $value) - carp "find by key => value deprecated; please use a hashref instead"; - $hash = {@_}; + 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, $self->{attrs}->{where}; + + # Verify the query + my $query = \@unique_queries; + if (scalar @unique_queries == 0) { + # Handle cases where the ResultSet defines the query, or where the user is + # abusing find + $query = $input_query; + } + + # Run the query + if (keys %$attrs) { + my $rs = $self->search($query, $attrs); + $rs->_resolve; + return keys %{$rs->{_attrs}->{collapse}} ? $rs->next : $rs->single; } else { - $self->throw_exception( - "Arguments to find must be a hashref or match the number of columns in the " - . (exists $attrs->{key} ? "$attrs->{key} unique constraint" : "primary key") - ); + $self->_resolve; + return (keys %{$self->{_attrs}->{collapse}}) + ? $self->search($query)->next + : $self->single($query); } +} + +# _unique_queries +# +# Build a list of queries which satisfy unique constraints. + +sub _unique_queries { + my ($self, $query, $attrs) = @_; - # Check the hash we just parsed against our source's unique constraints my @constraint_names = exists $attrs->{key} ? ($attrs->{key}) : $self->result_source->unique_constraint_names; - carp "find now requires a primary key or unique constraint; none is defined on " - . $self->result_source->name unless @constraint_names; my @unique_queries; foreach my $name (@constraint_names) { my @unique_cols = $self->result_source->unique_constraint_columns($name); - my $unique_query = $self->_build_unique_query($hash, \@unique_cols); + my $unique_query = $self->_build_unique_query($query, \@unique_cols); + + next unless scalar keys %$unique_query; # Add the ResultSet's alias foreach my $key (grep { ! m/\./ } keys %$unique_query) { - my $alias = $self->{attrs}->{alias}; - $unique_query->{"$alias.$key"} = delete $unique_query->{$key}; + $unique_query->{"$self->{attrs}->{alias}.$key"} = delete $unique_query->{$key}; } - push @unique_queries, $unique_query if %$unique_query; + push @unique_queries, $unique_query; } - # Compatibility: if we didn't get a unique query, take what the user provided - if (%$hash and not @unique_queries) { - carp "find now requires values for the primary key or a unique constraint" - . "; please use the search method instead"; - push @unique_queries, $hash; - } - - # Handle cases where the ResultSet already defines the query - my $query = @unique_queries ? \@unique_queries : undef; - - # Run the query - if (keys %$attrs) { - my $rs = $self->search($query, $attrs); - $rs->_resolve; - return keys %{$rs->{_attrs}->{collapse}} ? $rs->next : $rs->single; - } - else { - $self->_resolve; - return (keys %{$self->{_attrs}->{collapse}}) - ? $self->search($query)->next - : $self->single($query); - } + return @unique_queries; } # _build_unique_query @@ -460,12 +467,87 @@ sub single { } } +# use Data::Dumper; warn Dumper $attrs->{where}; + unless ($self->_is_unique_query($attrs->{where})) { + carp "Query not guarnteed 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); +# use Data::Dumper; warn Dumper $collapsed; + + foreach my $name ($self->result_source->unique_constraint_names) { + my @unique_cols = map { "$self->{attrs}->{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 = "$self->{attrs}->{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) = @_; + + # Accumulate fields in the AST + $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}; + } +# warn Dumper $collapsed; + } + } + + return $collapsed; +} + =head2 get_column =over 4 @@ -1302,8 +1384,8 @@ sub create { $class->find_or_create({ key => $val, ... }); -Searches for a record matching the search condition; if it doesn't find one, -creates one and returns that instead. +Tries to find a record based on its primary key or unique constraint; if none +is found, creates one and returns that instead. my $cd = $schema->resultset('CD')->find_or_create({ cdid => 5, @@ -1379,15 +1461,15 @@ unique constraints, see L. sub update_or_create { my $self = shift; my $attrs = (@_ > 1 && ref $_[$#_] eq 'HASH' ? pop(@_) : {}); - my $hash = ref $_[0] eq 'HASH' ? shift : {@_}; + my $cond = ref $_[0] eq 'HASH' ? shift : {@_}; - my $row = $self->find($hash, $attrs); + my $row = $self->find($cond); if (defined $row) { - $row->update($hash); + $row->update($cond); return $row; } - return $self->create($hash); + return $self->create($cond); } =head2 get_cache