X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FDBIx%2FClass%2FResultSet.pm;h=89600cebcccd44e397c93b2f6dd9b7587c58c174;hb=addaea392490128eb78b8d5193f0c933a3922717;hp=9b5a42b7fe830ee813f9acb2126f2683b373c4b0;hpb=c295dc9ed4703d80c2ecfdad613e6006249a0c72;p=dbsrgits%2FDBIx-Class-Historic.git diff --git a/lib/DBIx/Class/ResultSet.pm b/lib/DBIx/Class/ResultSet.pm index 9b5a42b..89600ce 100644 --- a/lib/DBIx/Class/ResultSet.pm +++ b/lib/DBIx/Class/ResultSet.pm @@ -277,6 +277,9 @@ Additionally, you can specify the columns explicitly by name: 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. @@ -296,23 +299,29 @@ sub find { ) unless @cols; # Parse out a hashref from input - my $query; + my $input_query; if (ref $_[0] eq 'HASH') { - $query = { %{$_[0]} }; + $input_query = { %{$_[0]} }; } elsif (@_ == @cols) { - $query = {}; - @{$query}{@cols} = @_; + $input_query = {}; + @{$input_query}{@cols} = @_; } else { # Compatibility: Allow e.g. find(id => $value) - carp "find by key => value deprecated; please use a hashref instead"; - $query = {@_}; + carp "Find by key => value deprecated; please use a hashref instead"; + $input_query = {@_}; } - # Add the ResultSet's alias - foreach my $key (grep { ! m/\./ } keys %$query) { - $query->{"$self->{attrs}{alias}.$key"} = delete $query->{$key}; + 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 @@ -329,6 +338,35 @@ sub find { } } +# _unique_queries +# +# Build a list of queries which satisfy unique constraints. + +sub _unique_queries { + my ($self, $query, $attrs) = @_; + + my @constraint_names = exists $attrs->{key} + ? ($attrs->{key}) + : $self->result_source->unique_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($query, \@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}; + } + + push @unique_queries, $unique_query; + } + + return @unique_queries; +} + # _build_unique_query # # Constrain the specified query hash based on the specified column names. @@ -429,12 +467,84 @@ 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) { + next unless exists $seen{$key}; # Additional constraints are okay + $seen{$key} = 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 @@ -1271,8 +1381,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, @@ -1348,34 +1458,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 @constraint_names = exists $attrs->{key} - ? ($attrs->{key}) - : $self->result_source->unique_constraint_names; - $self->throw_exception( - "update_or_create requires a primary key or unique constraint; none is defined on " - . $self->result_source->name - ) unless @constraint_names; + my $cond = ref $_[0] eq 'HASH' ? shift : {@_}; - 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); - - push @unique_queries, $unique_query - if keys %$unique_query == @unique_cols; - } - - if (@unique_queries) { - my $row = $self->single(\@unique_queries); - if (defined $row) { - $row->update($hash); - return $row; - } + my $row = $self->find($cond); + if (defined $row) { + $row->update($cond); + return $row; } - return $self->create($hash); + return $self->create($cond); } =head2 get_cache