Make select always equal group by on count subqueries
[dbsrgits/DBIx-Class-Historic.git] / lib / DBIx / Class / ResultSet.pm
index cc87db1..42b100a 100644 (file)
@@ -1150,7 +1150,7 @@ on the resultset and counts the results of that.
 
 =cut
 
-my @count_via_subq_attrs = qw/join seen_join group_by/;
+my @count_via_subq_attrs = qw/join seen_join prefetch group_by/;
 sub count {
   my $self = shift;
   return $self->search(@_)->count if @_ and defined $_[0];
@@ -1173,14 +1173,21 @@ sub _count_subq {
 
   my $attrs = { %{$self->_resolved_attrs} };
 
-  my $select_cols = $attrs->{group_by} || [ map { "$attrs->{alias}.$_" } ($self->result_source->primary_columns) ];
+  # copy for the subquery, we need to do some adjustments to it too
+  my $sub_attrs = { %$attrs };
+
+  # these can not go in the subquery either
+  delete $sub_attrs->{$_} for qw/prefetch select +select as +as columns +columns/;
+
+  # force a group_by and the same set of columns (most databases require this)
+  $sub_attrs->{columns} = $sub_attrs->{group_by} ||= [ map { "$attrs->{alias}.$_" } ($self->result_source->primary_columns) ];
+
   $attrs->{from} = [{
-    count_subq => $self->search ({}, { columns => $select_cols, group_by => $select_cols })
-                         ->as_query
+    count_subq => (ref $self)->new ($self->result_source, $sub_attrs )->as_query
   }];
 
-  # the subquery above will integrate everything, including 'where' and any pagers
-  delete $attrs->{$_} for (@count_via_subq_attrs, qw/where rows offset pager page/ );
+  # the subquery replaces this
+  delete $attrs->{where};
 
   return $self->__count ($attrs);
 }
@@ -1205,12 +1212,13 @@ sub __count {
 
   $attrs ||= { %{$self->{attrs}} };
 
+  # take off any subquery attrs (they'd be incorporated in the subquery),
+  # any column specs, any pagers, record_filter is cdbi, and no point of ordering a count
+  delete $attrs->{$_} for (@count_via_subq_attrs, qw/columns +columns select +select as +as rows offset page pager order_by record_filter/);
+
   $attrs->{select} = { count => '*' };
   $attrs->{as} = [qw/count/];
 
-  # take off any pagers, record_filter is cdbi, and no point of ordering a count
-  delete $attrs->{$_} for qw/rows offset page pager order_by record_filter/;
-
   my $tmp_rs = (ref $self)->new($self->result_source, $attrs);
   my ($count) = $tmp_rs->cursor->next;
 
@@ -2328,7 +2336,7 @@ sub related_resultset {
       "search_related: result source '" . $self->result_source->source_name .
         "' has no such relationship $rel")
       unless $rel_obj;
-    
+
     my ($from,$seen) = $self->_resolve_from($rel);
 
     my $join_count = $seen->{$rel};
@@ -2421,28 +2429,33 @@ sub current_source_alias {
   return ($self->{attrs} || {})->{alias} || 'me';
 }
 
+# This code is called by search_related, and makes sure there
+# is clear separation between the joins before, during, and
+# after the relationship. This information is needed later
+# in order to properly resolve prefetch aliases (any alias
+# with a relation_chain_depth less than the depth of the
+# current prefetch is not considered)
 sub _resolve_from {
   my ($self, $extra_join) = @_;
   my $source = $self->result_source;
   my $attrs = $self->{attrs};
-  
+
   my $from = $attrs->{from}
     || [ { $attrs->{alias} => $source->from } ];
     
   my $seen = { %{$attrs->{seen_join}||{}} };
 
-  my $join = ($attrs->{join}
-               ? [ $attrs->{join}, $extra_join ]
-               : $extra_join);
-
   # we need to take the prefetch the attrs into account before we 
   # ->resolve_join as otherwise they get lost - captainL
-  my $merged = $self->_merge_attr( $join, $attrs->{prefetch} );
+  my $merged = $self->_merge_attr( $attrs->{join}, $attrs->{prefetch} );
+
+  push @$from, $source->resolve_join($merged, $attrs->{alias}, $seen) if ($merged);
 
-  $from = [
-    @$from,
-    ($join ? $source->resolve_join($merged, $attrs->{alias}, $seen) : ()),
-  ];
+  ++$seen->{-relation_chain_depth};
+
+  push @$from, $source->resolve_join($extra_join, $attrs->{alias}, $seen);
+
+  ++$seen->{-relation_chain_depth};
 
   return ($from,$seen);
 }
@@ -2564,12 +2577,12 @@ sub _resolved_attrs {
   if ( my $prefetch = delete $attrs->{prefetch} ) {
     $prefetch = $self->_merge_attr( {}, $prefetch );
     my @pre_order;
-    my $seen = { %{ $attrs->{seen_join} || {} } };
     foreach my $p ( ref $prefetch eq 'ARRAY' ? @$prefetch : ($prefetch) ) {
 
       # bring joins back to level of current class
+      my $join_map = $self->_joinpath_aliases ($attrs->{from}, $attrs->{seen_join});
       my @prefetch =
-        $source->resolve_prefetch( $p, $alias, $seen, \@pre_order, $collapse );
+        $source->resolve_prefetch( $p, $alias, $join_map, \@pre_order, $collapse );
       push( @{ $attrs->{select} }, map { $_->[0] } @prefetch );
       push( @{ $attrs->{as} },     map { $_->[1] } @prefetch );
     }
@@ -2577,14 +2590,32 @@ sub _resolved_attrs {
   }
   $attrs->{collapse} = $collapse;
 
-  if ( $attrs->{page} ) {
-    $attrs->{offset} ||= 0;
-    $attrs->{offset} += ( $attrs->{rows} * ( $attrs->{page} - 1 ) );
+  if ( $attrs->{page} and not defined $attrs->{offset} ) {
+    $attrs->{offset} = ( $attrs->{rows} * ( $attrs->{page} - 1 ) );
   }
 
   return $self->{_attrs} = $attrs;
 }
 
+sub _joinpath_aliases {
+  my ($self, $fromspec, $seen) = @_;
+
+  my $paths = {};
+  return $paths unless ref $fromspec eq 'ARRAY';
+
+  for my $j (@$fromspec) {
+
+    next if ref $j ne 'ARRAY';
+    next if $j->[0]{-relation_chain_depth} < ( $seen->{-relation_chain_depth} || 0);
+
+    my $p = $paths;
+    $p = $p->{$_} ||= {} for @{$j->[0]{-join_path}};
+    push @{$p->{-join_aliases} }, $j->[0]{-join_alias};
+  }
+
+  return $paths;
+}
+
 sub _rollout_attr {
   my ($self, $attr) = @_;