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=e94782f6e121060142aeb839bb04a2be143b8fad;hpb=9d29cf57a7ed1b68277ff2b7ee58d46112d8c384;p=dbsrgits%2FDBIx-Class.git diff --git a/lib/DBIx/Class/ResultSet.pm b/lib/DBIx/Class/ResultSet.pm index e94782f..8e44f82 100644 --- a/lib/DBIx/Class/ResultSet.pm +++ b/lib/DBIx/Class/ResultSet.pm @@ -6,6 +6,7 @@ use overload '0+' => \&count, 'bool' => sub { 1; }, fallback => 1; +use Carp::Clan qw/^DBIx::Class/; use Data::Page; use Storable; use Data::Dumper; @@ -274,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. @@ -289,55 +290,40 @@ 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} = @_; - } - elsif (@_) { - # For backwards compatibility - $hash = {@_}; + $input_query = {}; + @{$input_query}{@cols} = @_; } 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") - ); + # Compatibility: Allow e.g. find(id => $value) + carp "Find by key => value deprecated; please use a hashref instead"; + $input_query = {@_}; } - # 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; - $self->throw_exception( - "Can't find unless a primary key or unique constraint is defined" - ) 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); - - # 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}; - } + 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}; - push @unique_queries, $unique_query if %$unique_query; + # 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; } - # 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); @@ -352,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. @@ -452,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 @@ -631,6 +721,18 @@ sub _resolve { $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 ]; + } my $collapse = $attrs->{collapse} || {}; if (my $prefetch = delete $attrs->{prefetch}) { @@ -1282,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, @@ -1359,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 @@ -1503,6 +1605,11 @@ 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'. +Please note that if you have quoting enabled (see +L) you will need to do C<\'year DESC' > to +specify an order. (The scalar ref causes it to be passed as raw sql to the DB, +so you will need to manually quote things as appropriate.) + =head2 columns =over 4 @@ -1558,6 +1665,23 @@ When you use function/stored procedure names and do not supply an C attribute, the column names returned are storage-dependent. E.g. MySQL would return a column named C in the above example. +=head2 +select + +=over 4 + +Indicates additional columns to be selected from storage. Works the same as +L