X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FDBIx%2FClass%2FResultSet.pm;h=9c278fee6fb7eb0883267bcf9054869eff0b5b3a;hb=0fcfe456c1255339f94fa1c218451760e65d24bc;hp=fae8f01f598cb7ccf0710d64d5d083e7fce5b23c;hpb=e2db8319f8e463bc3136e4aec9372a1e8305ac26;p=dbsrgits%2FDBIx-Class.git diff --git a/lib/DBIx/Class/ResultSet.pm b/lib/DBIx/Class/ResultSet.pm index fae8f01..9c278fe 100644 --- a/lib/DBIx/Class/ResultSet.pm +++ b/lib/DBIx/Class/ResultSet.pm @@ -7,6 +7,7 @@ use overload 'bool' => "_bool", fallback => 1; use Carp::Clan qw/^DBIx::Class/; +use DBIx::Class::Exception; use Data::Page; use Storable; use DBIx::Class::ResultSetColumn; @@ -356,9 +357,9 @@ sub search_rs { } my $rs = (ref $self)->new($self->result_source, $new_attrs); - if ($rows) { - $rs->set_cache($rows); - } + + $rs->set_cache($rows) if ($rows); + return $rs; } @@ -518,7 +519,7 @@ sub find { # in ::Relationship::Base::search_related (the row method), and furthermore # the relationship is of the 'single' type. This means that the condition # provided by the relationship (already attached to $self) is sufficient, - # as there can be only one row in the databse that would satisfy the + # as there can be only one row in the databse that would satisfy the # relationship } else { @@ -529,7 +530,7 @@ sub find { } # Run the query - my $rs = $self->search ($query, $attrs); + my $rs = $self->search ($query, {result_class => $self->result_class, %$attrs}); if (keys %{$rs->_resolved_attrs->{collapse}}) { my $row = $rs->next; carp "Query returned more than one row" if $rs->next; @@ -570,12 +571,16 @@ sub _unique_queries { my $where = $self->_collapse_cond($self->{attrs}{where} || {}); my $num_where = scalar keys %$where; - my @unique_queries; + my (@unique_queries, %seen_column_combinations); 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); + my @constraint_cols = $self->result_source->unique_constraint_columns($name); + + my $constraint_sig = join "\x00", sort @constraint_cols; + next if $seen_column_combinations{$constraint_sig}++; - my $num_cols = scalar @unique_cols; + my $unique_query = $self->_build_unique_query($query, \@constraint_cols); + + my $num_cols = scalar @constraint_cols; my $num_query = scalar keys %$unique_query; my $total = $num_query + $num_where; @@ -1235,7 +1240,7 @@ sub _count_rs { my $tmp_attrs = { %$attrs }; - # take off any limits, record_filter is cdbi, and no point of ordering a count + # take off any limits, record_filter is cdbi, and no point of ordering a count delete $tmp_attrs->{$_} for (qw/select as rows offset order_by record_filter/); # overwrite the selector (supplied by the storage) @@ -1264,10 +1269,10 @@ sub _count_subq_rs { my $sub_attrs = { %$attrs }; # extra selectors do not go in the subquery and there is no point of ordering it - delete $sub_attrs->{$_} for qw/collapse prefetch_select select as order_by/; + delete $sub_attrs->{$_} for qw/collapse select _prefetch_select as order_by/; - # if we prefetch, we group_by primary keys only as this is what we would get out of the rs via ->next/->all - # clobber old group_by regardless + # if we prefetch, we group_by primary keys only as this is what we would get out + # of the rs via ->next/->all. We DO WANT to clobber old group_by regardless if ( keys %{$attrs->{collapse}} ) { $sub_attrs->{group_by} = [ map { "$attrs->{alias}.$_" } ($rsrc->primary_columns) ] } @@ -1315,9 +1320,12 @@ sub _count_subq_rs { sub _switch_to_inner_join_if_needed { my ($self, $from, $alias) = @_; + # subqueries and other oddness is naturally not supported return $from if ( ref $from ne 'ARRAY' || + @$from <= 1 + || ref $from->[0] ne 'HASH' || ! $from->[0]{-alias} @@ -1325,10 +1333,6 @@ sub _switch_to_inner_join_if_needed { $from->[0]{-alias} eq $alias ); - # this would be the case with a subquery - we'll never find - # the target as it is not in the parseable part of {from} - return $from if @$from == 1; - my $switch_branch; JOINSCAN: for my $j (@{$from}[1 .. $#$from]) { @@ -1510,7 +1514,8 @@ sub _rs_update_delete { if (my $g = $attrs->{group_by}) { my @current_group_by = map { $_ =~ /\./ ? $_ : "$attrs->{alias}.$_" } - (ref $g eq 'ARRAY' ? @$g : $g ); + @$g + ; if ( join ("\x00", sort @current_group_by) @@ -1789,10 +1794,19 @@ sub populate { } return wantarray ? @created : \@created; } else { - my ($first, @rest) = @$data; + my $first = $data->[0]; + + # if a column is a registered relationship, and is a non-blessed hash/array, consider + # it relationship data + my (@rels, @columns); + for (keys %$first) { + my $ref = ref $first->{$_}; + $self->result_source->has_relationship($_) && ($ref eq 'ARRAY' or $ref eq 'HASH') + ? push @rels, $_ + : push @columns, $_ + ; + } - my @names = grep {!ref $first->{$_}} keys %$first; - my @rels = grep { $self->result_source->has_relationship($_) } keys %$first; my @pks = $self->result_source->primary_columns; ## do the belongs_to relationships @@ -1821,17 +1835,15 @@ sub populate { delete $data->[$index]->{$rel}; $data->[$index] = {%{$data->[$index]}, %$related}; - push @names, keys %$related if $index == 0; + push @columns, keys %$related if $index == 0; } } ## do bulk insert on current row - my @values = map { [ @$_{@names} ] } @$data; - $self->result_source->storage->insert_bulk( $self->result_source, - \@names, - \@values, + \@columns, + [ map { [ @$_{@columns} ] } @$data ], ); ## do the has_many relationships @@ -1840,7 +1852,7 @@ sub populate { foreach my $rel (@rels) { next unless $item->{$rel} && ref $item->{$rel} eq "ARRAY"; - my $parent = $self->find(map {{$_=>$item->{$_}} } @pks) + my $parent = $self->find({map { $_ => $item->{$_} } @pks}) || $self->throw_exception('Cannot find the relating object.'); my $child = $parent->$rel; @@ -2192,13 +2204,14 @@ You most likely want this method when looking for existing rows using a unique constraint that is not the primary key, or looking for related rows. -If you want objects to be saved immediately, use L instead. +If you want objects to be saved immediately, use L +instead. -B: C is probably not what you want when creating a -new row in a table that uses primary keys supplied by the -database. Passing in a primary key column with a value of I -will cause L to attempt to search for a row with a value of -I. +B: Take care when using C with a table having +columns with default values that you intend to be automatically +supplied by the database (e.g. an auto_increment primary key column). +In normal usage, the value of such columns should NOT be included at +all in the call to C, even when set to C. =cut @@ -2233,12 +2246,15 @@ store. If the appropriate relationships are set up, foreign key fields can also be passed an object representing the foreign row, and the value will be set to its primary key. -To create related objects, pass a hashref for the value if the related -item is a foreign key relationship (L), -and use the name of the relationship as the key. (NOT the name of the field, -necessarily). For C and C relationships, pass an arrayref -of hashrefs containing the data for each of the rows to create in the foreign -tables, again using the relationship name as the key. +To create related objects, pass a hashref of related-object column values +B. If the relationship is of type C +(L) - pass an arrayref of hashrefs. +The process will correctly identify columns holding foreign keys, and will +transparrently populate them from the keys of the corresponding relation. +This can be applied recursively, and will work correctly for a structure +with an arbitrary depth and width, as long as the relationships actually +exists and the correct column data has been supplied. + Instead of hashrefs of plain related data (key/value pairs), you may also pass new or inserted objects. New objects (not inserted yet, see @@ -2275,6 +2291,19 @@ Cresultset. Note Hashref. } }); +=over + +=item WARNING + +When subclassing ResultSet never attempt to override this method. Since +it is a simple shortcut for C<< $self->new_result($attrs)->insert >>, a +lot of the internals simply never call it, so your override will be +bypassed more often than not. Override either L +or L depending on how early in the +L process you need to intervene. + +=back + =cut sub create { @@ -2324,11 +2353,11 @@ condition. Another process could create a record in the table after the find has completed and before the create has started. To avoid this problem, use find_or_create() inside a transaction. -B: C is probably not what you want when creating -a new row in a table that uses primary keys supplied by the -database. Passing in a primary key column with a value of I -will cause L to attempt to search for a row with a value of -I. +B: Take care when using C with a table having +columns with default values that you intend to be automatically +supplied by the database (e.g. an auto_increment primary key column). +In normal usage, the value of such columns should NOT be included at +all in the call to C, even when set to C. See also L and L. For information on how to declare unique constraints, see L. @@ -2391,11 +2420,11 @@ If the C is specified as C, it searches only on the primary key. See also L and L. For information on how to declare unique constraints, see L. -B: C is probably not what you want when -looking for a row in a table that uses primary keys supplied by the -database, unless you actually have a key value. Passing in a primary -key column with a value of I will cause L to attempt to -search for a row with a value of I. +B: Take care when using C with a table having +columns with default values that you intend to be automatically +supplied by the database (e.g. an auto_increment primary key column). +In normal usage, the value of such columns should NOT be included at +all in the call to C, even when set to C. =cut @@ -2452,7 +2481,13 @@ For example: $cd->insert; } -See also L, L and L. +B: Take care when using C with a table having +columns with default values that you intend to be automatically +supplied by the database (e.g. an auto_increment primary key column). +In normal usage, the value of such columns should NOT be included at +all in the call to C, even when set to C. + +See also L, L and L. =cut @@ -2536,6 +2571,23 @@ sub clear_cache { shift->set_cache(undef); } +=head2 is_paged + +=over 4 + +=item Arguments: none + +=item Return Value: true, if the resultset has been paginated + +=back + +=cut + +sub is_paged { + my ($self) = @_; + return !!$self->{attrs}{page}; +} + =head2 related_resultset =over 4 @@ -2683,8 +2735,8 @@ sub _chain_relationship { }]; my $seen = { %{$attrs->{seen_join} || {} } }; - my $jpath = ($attrs->{seen_join} && keys %{$attrs->{seen_join}}) - ? $from->[-1][0]{-join_path} + my $jpath = ($attrs->{seen_join} && keys %{$attrs->{seen_join}}) + ? $from->[-1][0]{-join_path} : []; @@ -2762,24 +2814,35 @@ 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($attrs->{columns}) eq 'ARRAY' ) ? @{ delete $attrs->{columns}} : (delete $attrs->{columns} || $source->columns ); + + my @cols = ( ref($attrs->{columns}) eq 'ARRAY' ) + ? @{ delete $attrs->{columns}} + : ( + ( delete $attrs->{columns} ) + || + $source->columns + ) + ; + + @colbits = map { + ( ref($_) eq 'HASH' ) + ? $_ + : { + ( + /^\Q${alias}.\E(.+)$/ + ? "$1" + : "$_" + ) + => + ( + /\./ + ? "$_" + : "${alias}.$_" + ) + } + } @cols; } + # add the additional columns on foreach ( 'include_columns', '+columns' ) { push @colbits, map { @@ -2837,7 +2900,7 @@ sub _resolved_attrs { if ( $attrs->{join} || $attrs->{prefetch} ) { - $self->throw_exception ('join/prefetch can not be used with a literal scalarref {from}') + $self->throw_exception ('join/prefetch can not be used with a custom {from}') if ref $attrs->{from} ne 'ARRAY'; my $join = delete $attrs->{join} || {}; @@ -2869,10 +2932,21 @@ sub _resolved_attrs { ); } - if ($attrs->{group_by} and ! ref $attrs->{group_by}) { + if ($attrs->{group_by} and ref $attrs->{group_by} ne 'ARRAY') { $attrs->{group_by} = [ $attrs->{group_by} ]; } + # generate the distinct induced group_by early, as prefetch will be carried via a + # subquery (since a group_by is present) + if (delete $attrs->{distinct}) { + if ($attrs->{group_by}) { + carp ("Useless use of distinct on a grouped resultset ('distinct' is ignored when a 'group_by' is present)"); + } + else { + $attrs->{group_by} = [ grep { !ref($_) || (ref($_) ne 'HASH') } @{$attrs->{select}} ]; + } + } + $attrs->{collapse} ||= {}; if ( my $prefetch = delete $attrs->{prefetch} ) { $prefetch = $self->_merge_attr( {}, $prefetch ); @@ -2884,24 +2958,21 @@ sub _resolved_attrs { my @prefetch = $source->_resolve_prefetch( $prefetch, $alias, $join_map, $prefetch_ordering, $attrs->{collapse} ); - $attrs->{prefetch_select} = [ map { $_->[0] } @prefetch ]; - push @{ $attrs->{select} }, @{$attrs->{prefetch_select}}; + # we need to somehow mark which columns came from prefetch + $attrs->{_prefetch_select} = [ map { $_->[0] } @prefetch ]; + + push @{ $attrs->{select} }, @{$attrs->{_prefetch_select}}; push @{ $attrs->{as} }, (map { $_->[1] } @prefetch); push( @{$attrs->{order_by}}, @$prefetch_ordering ); $attrs->{_collapse_order_by} = \@$prefetch_ordering; } - - if (delete $attrs->{distinct}) { - $attrs->{group_by} ||= [ grep { !ref($_) || (ref($_) ne 'HASH') } @{$attrs->{select}} ]; - } - # if both page and offset are specified, produce a combined offset # even though it doesn't make much sense, this is what pre 081xx has # been doing if (my $page = delete $attrs->{page}) { - $attrs->{offset} = + $attrs->{offset} = ($attrs->{rows} * ($page - 1)) + ($attrs->{offset} || 0) @@ -2926,7 +2997,7 @@ sub _joinpath_aliases { for my $j (@$fromspec) { next if ref $j ne 'ARRAY'; - next if $j->[0]{-relation_chain_depth} < $cur_depth; + next if ($j->[0]{-relation_chain_depth} || 0) < $cur_depth; my $jpath = $j->[0]{-join_path}; @@ -2980,6 +3051,13 @@ sub _rollout_hash { sub _calculate_score { my ($self, $a, $b) = @_; + if (defined $a xor defined $b) { + return 0; + } + elsif (not defined $a) { + return 1; + } + if (ref $b eq 'HASH') { my ($b_key) = keys %{$b}; if (ref $a eq 'HASH') { @@ -3061,12 +3139,13 @@ See L for details. sub throw_exception { my $self=shift; + if (ref $self && $self->_source_handle->schema) { $self->_source_handle->schema->throw_exception(@_) - } else { - croak(@_); } - + else { + DBIx::Class::Exception->throw(@_); + } } # XXX: FIXME: Attributes docs need clearing up @@ -3088,10 +3167,15 @@ These are in no particular order: =back -Which column(s) to order the results by. If a single column name, or -an arrayref of names is supplied, the argument is passed through -directly to SQL. The hashref syntax allows for connection-agnostic -specification of ordering direction: +Which column(s) to order the results by. + +[The full list of suitable values is documented in +L; the following is a summary of +common options.] + +If a single column name, or an arrayref of names is supplied, the +argument is passed through directly to SQL. The hashref syntax allows +for connection-agnostic specification of ordering direction: For descending order: @@ -3178,6 +3262,9 @@ 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. +B You will almost always need a corresponding 'as' entry when you use +'select'. + =head2 +select =over 4 @@ -3370,6 +3457,42 @@ with that artist is given below (assuming many-to-many from artists to tags): B If you specify a C attribute, the C and C