Store the final calculated select args in the original $rs
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / Storage / DBIHacks.pm
index fd290f0..d4f4058 100644 (file)
@@ -34,8 +34,8 @@ sub _prune_unused_joins {
 
   my $aliastypes = $self->_resolve_aliastypes_from_select_args(@_);
 
-  # don't care
-  delete $aliastypes->{joining};
+  my $orig_joins = delete $aliastypes->{joining};
+  my $orig_multiplying = $aliastypes->{multiplying};
 
   # a grouped set will not be affected by amount of rows. Thus any
   # {multiplying} joins can go
@@ -56,13 +56,17 @@ sub _prune_unused_joins {
 
   for my $j (@{$from}[1..$#$from]) {
     push @newfrom, $j if (
-      (! $j->[0]{-alias}) # legacy crap
+      (! defined $j->[0]{-alias}) # legacy crap
         ||
       $need_joins{$j->[0]{-alias}}
     );
   }
 
-  return \@newfrom;
+  return ( \@newfrom, {
+    multiplying => { map { $need_joins{$_} ? ($_  => $orig_multiplying->{$_}) : () } keys %$orig_multiplying },
+    %$aliastypes,
+    joining => { map { $_ => $orig_joins->{$_} } keys %need_joins },
+  } );
 }
 
 #
@@ -79,10 +83,10 @@ sub _adjust_select_args_for_complex_prefetch {
 
   # generate inner/outer attribute lists, remove stuff that doesn't apply
   my $outer_attrs = { %$attrs };
-  delete $outer_attrs->{$_} for qw/where bind rows offset group_by _grouped_by_distinct having/;
+  delete @{$outer_attrs}{qw(where bind rows offset group_by _grouped_by_distinct having)};
 
   my $inner_attrs = { %$attrs };
-  delete $inner_attrs->{$_} for qw/from for collapse select as _related_results_construction/;
+  delete @{$inner_attrs}{qw(from for collapse select as _related_results_construction)};
 
   # there is no point of ordering the insides if there is no limit
   delete $inner_attrs->{order_by} if (
@@ -146,7 +150,7 @@ sub _adjust_select_args_for_complex_prefetch {
   # We can not just fetch everything because a potential has_many restricting
   # join collapse *will not work* on heavy data types.
   my $connecting_aliastypes = $self->_resolve_aliastypes_from_select_args(
-    [grep { ref($_) eq 'ARRAY' or ref($_) eq 'HASH' } @{$from}[$root_node_offset .. $#$from]],
+    $from,
     [],
     $where,
     $inner_attrs
@@ -175,18 +179,16 @@ sub _adjust_select_args_for_complex_prefetch {
     local $self->{_use_join_optimizer} = 1;
 
     # throw away multijoins since we def. do not care about those inside the subquery
-    my $inner_from = $self->_prune_unused_joins ($from, $inner_select, $where, {
+    my ($inner_from, $inner_aliastypes) = $self->_prune_unused_joins ($from, $inner_select, $where, {
       %$inner_attrs, _force_prune_multiplying_joins => 1
     });
 
-    my $inner_aliastypes =
-      $self->_resolve_aliastypes_from_select_args( $inner_from, $inner_select, $where, $inner_attrs );
-
     # uh-oh a multiplier (which is not us) left in, this is a problem
     if (
       $inner_aliastypes->{multiplying}
         and
-      !$inner_aliastypes->{grouping}  # if there are groups - assume user knows wtf they are up to
+      # if there are user-supplied groups - assume user knows wtf they are up to
+      ( ! $inner_aliastypes->{grouping} or $inner_attrs->{_grouped_by_distinct} )
         and
       my @multipliers = grep { $_ ne $root_alias } keys %{$inner_aliastypes->{multiplying}}
     ) {
@@ -200,9 +202,11 @@ sub _adjust_select_args_for_complex_prefetch {
       ) {
 
         my $unprocessed_order_chunks;
-        ($inner_attrs->{group_by}, $unprocessed_order_chunks) = $self->_group_over_selection (
-          $inner_from, $inner_select, $inner_attrs->{order_by}
-        );
+        ($inner_attrs->{group_by}, $unprocessed_order_chunks) = $self->_group_over_selection ({
+          %$inner_attrs,
+          from => $inner_from,
+          select => $inner_select,
+        });
 
         $self->throw_exception (
           'A required group_by clause could not be constructed automatically due to a complex '
@@ -221,17 +225,6 @@ sub _adjust_select_args_for_complex_prefetch {
         # for DESC, and group_by the root columns. The end result should be
         # exactly what we expect
 
-        # FIXME REMOVE LATER - (just a sanity check)
-        if (defined ( my $impostor = first
-          { $_ ne $root_alias }
-          keys %{ $inner_aliastypes->{selecting} }
-        ) ) {
-          $self->throw_exception(sprintf
-            'Unexpected inner selection during complex prefetch (%s)...',
-            join ', ', keys %{ $inner_aliastypes->{joining}{$impostor}{-seen_columns} || {} }
-          );
-        }
-
         # supplement the main selection with pks if not already there,
         # as they will have to be a part of the group_by to colapse
         # things properly
@@ -298,9 +291,11 @@ sub _adjust_select_args_for_complex_prefetch {
 
         # do not care about leftovers here - it will be all the functions
         # we just created
-        ($inner_attrs->{group_by}) = $self->_group_over_selection (
-          $inner_from, $inner_select, $inner_attrs->{order_by}
-        );
+        ($inner_attrs->{group_by}) = $self->_group_over_selection ({
+          %$inner_attrs,
+          from => $inner_from,
+          select => $inner_select,
+        });
       }
     }
 
@@ -334,7 +329,7 @@ sub _adjust_select_args_for_complex_prefetch {
 
   # we may not be the head
   if ($root_node_offset) {
-    # first generate the outer_from, up to the substitution point
+    # first generate the outer_from, up and including the substitution point
     @outer_from = splice @$from, 0, $root_node_offset;
 
     push @outer_from, [
@@ -354,11 +349,11 @@ sub _adjust_select_args_for_complex_prefetch {
     };
   }
 
-  shift @$from; # it's replaced in @outer_from already
+  shift @$from; # what we just replaced above
 
   # scan the *remaining* from spec against different attributes, and see which joins are needed
   # in what role
-  my $outer_aliastypes =
+  my $outer_aliastypes = $outer_attrs->{_aliastypes} =
     $self->_resolve_aliastypes_from_select_args( $from, $outer_select, $where, $outer_attrs );
 
   # unroll parents
@@ -386,9 +381,11 @@ sub _adjust_select_args_for_complex_prefetch {
 
   if ( $need_outer_group_by and $attrs->{_grouped_by_distinct} ) {
     my $unprocessed_order_chunks;
-    ($outer_attrs->{group_by}, $unprocessed_order_chunks) = $self->_group_over_selection (
-      \@outer_from, $outer_select, $outer_attrs->{order_by}
-    );
+    ($outer_attrs->{group_by}, $unprocessed_order_chunks) = $self->_group_over_selection ({
+      %$outer_attrs,
+      from => \@outer_from,
+      select => $outer_select,
+    });
 
     $self->throw_exception (
       'A required group_by clause could not be constructed automatically due to a complex '
@@ -571,43 +568,49 @@ sub _resolve_aliastypes_from_select_args {
 
 # This is the engine behind { distinct => 1 }
 sub _group_over_selection {
-  my ($self, $from, $select, $order_by) = @_;
+  my ($self, $attrs) = @_;
 
-  my $rs_column_list = $self->_resolve_column_info ($from);
+  my $colinfos = $self->_resolve_column_info ($attrs->{from});
 
   my (@group_by, %group_index);
 
   # the logic is: if it is a { func => val } we assume an aggregate,
   # otherwise if \'...' or \[...] we assume the user knows what is
   # going on thus group over it
-  for (@$select) {
+  for (@{$attrs->{select}}) {
     if (! ref($_) or ref ($_) ne 'HASH' ) {
       push @group_by, $_;
       $group_index{$_}++;
-      if ($rs_column_list->{$_} and $_ !~ /\./ ) {
+      if ($colinfos->{$_} and $_ !~ /\./ ) {
         # add a fully qualified version as well
-        $group_index{"$rs_column_list->{$_}{-source_alias}.$_"}++;
+        $group_index{"$colinfos->{$_}{-source_alias}.$_"}++;
       }
     }
   }
 
-  # add any order_by parts that are not already present in the group_by
+  # add any order_by parts *from the main source* that are not already
+  # present in the group_by
   # we need to be careful not to add any named functions/aggregates
   # i.e. order_by => [ ... { count => 'foo' } ... ]
   my @leftovers;
-  for ($self->_extract_order_criteria($order_by)) {
+  for ($self->_extract_order_criteria($attrs->{order_by})) {
     # only consider real columns (for functions the user got to do an explicit group_by)
     if (@$_ != 1) {
       push @leftovers, $_;
       next;
     }
     my $chunk = $_->[0];
-    my $colinfo = $rs_column_list->{$chunk} or do {
+
+    if (
+      !$colinfos->{$chunk}
+        or
+      $colinfos->{$chunk}{-source_alias} ne $attrs->{alias}
+    ) {
       push @leftovers, $_;
       next;
-    };
+    }
 
-    $chunk = "$colinfo->{-source_alias}.$chunk" if $chunk !~ /\./;
+    $chunk = $colinfos->{$chunk}{-fq_colname};
     push @group_by, $chunk unless $group_index{$chunk}++;
   }
 
@@ -621,14 +624,12 @@ sub _resolve_ident_sources {
   my ($self, $ident) = @_;
 
   my $alias2source = {};
-  my $rs_alias;
 
   # the reason this is so contrived is that $ident may be a {from}
   # structure, specifying multiple tables to join
   if ( blessed $ident && $ident->isa("DBIx::Class::ResultSource") ) {
     # this is compat mode for insert/update/delete which do not deal with aliases
     $alias2source->{me} = $ident;
-    $rs_alias = 'me';
   }
   elsif (ref $ident eq 'ARRAY') {
 
@@ -636,7 +637,6 @@ sub _resolve_ident_sources {
       my $tabinfo;
       if (ref $_ eq 'HASH') {
         $tabinfo = $_;
-        $rs_alias = $tabinfo->{-alias};
       }
       if (ref $_ eq 'ARRAY' and ref $_->[0] eq 'HASH') {
         $tabinfo = $_->[0];
@@ -647,7 +647,7 @@ sub _resolve_ident_sources {
     }
   }
 
-  return ($alias2source, $rs_alias);
+  return $alias2source;
 }
 
 # Takes $ident, \@column_names
@@ -659,7 +659,7 @@ sub _resolve_ident_sources {
 # for all sources
 sub _resolve_column_info {
   my ($self, $ident, $colnames) = @_;
-  my ($alias2src, $root_alias) = $self->_resolve_ident_sources($ident);
+  my $alias2src = $self->_resolve_ident_sources($ident);
 
   my (%seen_cols, @auto_colnames);