X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FDBIx%2FClass%2FResultSet.pm;h=f8bed0e25d60a1670f31d9ee11f9104dba623a8e;hb=f685632da1dcff2c4d671f0c4b7338cbd8753b58;hp=d1db895af2418d217d29e623c8767c99d977a631;hpb=690c69e4639181658ed17c528eb79f3b0f2d2994;p=dbsrgits%2FDBIx-Class.git diff --git a/lib/DBIx/Class/ResultSet.pm b/lib/DBIx/Class/ResultSet.pm index d1db895..f8bed0e 100644 --- a/lib/DBIx/Class/ResultSet.pm +++ b/lib/DBIx/Class/ResultSet.pm @@ -957,7 +957,9 @@ sub next { sub _construct_object { my ($self, @row) = @_; - my $info = $self->_collapse_result($self->{_attrs}{as}, \@row); + + my $info = $self->_collapse_result($self->{_attrs}{as}, \@row) + or return (); my @new = $self->result_class->inflate_result($self->result_source, @$info); @new = $self->{_attrs}{record_filter}->(@new) if exists $self->{_attrs}{record_filter}; @@ -967,6 +969,14 @@ sub _construct_object { sub _collapse_result { my ($self, $as_proto, $row) = @_; + # if the first row that ever came in is totally empty - this means we got + # hit by a smooth^Wempty left-joined resultset. Just noop in that case + # instead of producing a {} + # + # Note the double-defined - $row may be [ 0, '' ] + # + return undef unless ( defined List::Util::first { defined $_ } (@$row) ); + my @copy = @$row; # 'foo' => [ undef, 'foo' ] @@ -1227,6 +1237,11 @@ sub _count_rs { $tmp_attrs->{select} = $rsrc->storage->_count_select ($rsrc, $tmp_attrs); $tmp_attrs->{as} = 'count'; + # read the comment on top of the actual function to see what this does + $tmp_attrs->{from} = $self->_switch_to_inner_join_if_needed ( + $tmp_attrs->{from}, $tmp_attrs->{alias} + ); + my $tmp_rs = $rsrc->resultset_class->new($rsrc, $tmp_attrs)->get_column ('count'); return $tmp_rs; @@ -1243,8 +1258,8 @@ sub _count_subq_rs { my $sub_attrs = { %$attrs }; - # these can not go in the subquery, and there is no point of ordering it - delete $sub_attrs->{$_} for qw/collapse select as order_by/; + # 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/; # 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 @@ -1254,6 +1269,11 @@ sub _count_subq_rs { $sub_attrs->{select} = $rsrc->storage->_subq_count_select ($rsrc, $sub_attrs); + # read the comment on top of the actual function to see what this does + $sub_attrs->{from} = $self->_switch_to_inner_join_if_needed ( + $sub_attrs->{from}, $sub_attrs->{alias} + ); + $attrs->{from} = [{ count_subq => $rsrc->resultset_class->new ($rsrc, $sub_attrs )->as_query }]; @@ -1265,6 +1285,93 @@ sub _count_subq_rs { } +# The DBIC relationship chaining implementation is pretty simple - every +# new related_relationship is pushed onto the {from} stack, and the {select} +# window simply slides further in. This means that when we count somewhere +# in the middle, we got to make sure that everything in the join chain is an +# actual inner join, otherwise the count will come back with unpredictable +# results (a resultset may be generated with _some_ rows regardless of if +# the relation which the $rs currently selects has rows or not). E.g. +# $artist_rs->cds->count - normally generates: +# SELECT COUNT( * ) FROM artist me LEFT JOIN cd cds ON cds.artist = me.artistid +# which actually returns the number of artists * (number of cds || 1) +# +# So what we do here is crawl {from}, determine if the current alias is at +# the top of the stack, and if not - make sure the chain is inner-joined down +# to the root. +# +sub _switch_to_inner_join_if_needed { + my ($self, $from, $alias) = @_; + + return $from if ( + ref $from ne 'ARRAY' + || + ref $from->[0] ne 'HASH' + || + ! $from->[0]{-alias} + || + $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_idx, $found_target); + + JOINSCAN: + for my $i (1 .. $#$from) { + + push @switch_idx, $i; + my $j = $from->[$i]; + my $jalias = $j->[0]{-alias}; + + # we found our current target - delete any siblings (same level joins) + # and bail out + if ($jalias eq $alias) { + $found_target++; + + my $cur_depth = $j->[0]{-relation_chain_depth}; + # we are -1, so look at -2 + while (@switch_idx > 1 + && $from->[$switch_idx[-2]][0]{-relation_chain_depth} == $cur_depth + ) { + splice @switch_idx, -2, 1; + } + + last JOINSCAN; + } + } + + # something else went wrong + return $from unless $found_target; + + # So it looks like we will have to switch some stuff around. + # local() is useless here as we will be leaving the scope + # anyway, and deep cloning is just too fucking expensive + # So replace the inner hashref manually + my @new_from; + my $sw_idx = { map { $_ => 1 } @switch_idx }; + + for my $i (0 .. $#$from) { + if ($sw_idx->{$i}) { + my %attrs = %{$from->[$i][0]}; + delete $attrs{-join_type}; + + push @new_from, [ + \%attrs, + @{$from->[$i]}[ 1 .. $#{$from->[$i]} ], + ]; + } + else { + push @new_from, $from->[$i]; + } + } + + return \@new_from; +} + + sub _bool { return 1; } @@ -1311,13 +1418,12 @@ sub all { my @obj; - # TODO: don't call resolve here if (keys %{$self->_resolved_attrs->{collapse}}) { -# if ($self->{attrs}{prefetch}) { - # Using $self->cursor->all is really just an optimisation. - # If we're collapsing has_many prefetches it probably makes - # very little difference, and this is cleaner than hacking - # _construct_object to survive the approach + # Using $self->cursor->all is really just an optimisation. + # If we're collapsing has_many prefetches it probably makes + # very little difference, and this is cleaner than hacking + # _construct_object to survive the approach + $self->cursor->reset; my @row = $self->cursor->next; while (@row) { push(@obj, $self->_construct_object(@row)); @@ -1330,6 +1436,7 @@ sub all { } $self->set_cache(\@obj) if $self->{attrs}{cache}; + return @obj; } @@ -1344,6 +1451,8 @@ sub all { =back Resets the resultset's cursor, so you can iterate through the elements again. +Implicitly resets the storage cursor, so a subsequent L will trigger +another query. =cut @@ -1925,16 +2034,25 @@ sub _is_deterministic_value { # of the attributes supplied # # used to determine if a subquery is neccessary +# +# supports some virtual attributes: +# -join +# This will scan for any joins being present on the resultset. +# It is not a mere key-search but a deep inspection of {from} +# sub _has_resolved_attr { my ($self, @attr_names) = @_; my $attrs = $self->_resolved_attrs; - my $join_check_req; + my %extra_checks; for my $n (@attr_names) { - ++$join_check_req if $n eq '-join'; + if (grep { $n eq $_ } (qw/-join/) ) { + $extra_checks{$n}++; + next; + } my $attr = $attrs->{$n}; @@ -1953,7 +2071,7 @@ sub _has_resolved_attr { # a resolved join is expressed as a multi-level from return 1 if ( - $join_check_req + $extra_checks{-join} and ref $attrs->{from} eq 'ARRAY' and @@ -2717,8 +2835,9 @@ sub _resolved_attrs { : [ $attrs->{order_by} ] ); } - else { - $attrs->{order_by} = []; + + if ($attrs->{group_by} and ! ref $attrs->{group_by}) { + $attrs->{group_by} = [ $attrs->{group_by} ]; } # If the order_by is otherwise empty - we will use this for TOP limit @@ -2740,8 +2859,9 @@ sub _resolved_attrs { my @prefetch = $source->_resolve_prefetch( $prefetch, $alias, $join_map, $prefetch_ordering, $attrs->{collapse} ); - push( @{ $attrs->{select} }, map { $_->[0] } @prefetch ); - push( @{ $attrs->{as} }, map { $_->[1] } @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;